std/os, x/os/socket: windows support, socket helpers, getpeername()

Socket I/O methods such as read, readv, write, writev, send, recv,
sendmsg, recvmsg have been generalized to read(buf, flags), write(buf,
flags), readVectorized(vectors, flags), and writeVectorized(vectors,
flags). There is still some work left to be done abstracting both
readVectorized and writeVectorized properly across platforms, which is
work to be done in a future PR.

Support for setting the linger timeout of a socket, querying the remote
address of a socket, setting whether or not keep-alive messages are to
be sent through a connection-oriented socket periodically depending on
host operating system settings has been added.

`std.io.Reader` and `std.io.Writer` wrappers around `Socket` has been
implemented, which wrap around Socket.read(buf, flags) and
Socket.write(buf, flags). Both wrappers may be provided flags which are
passed to Socket.read / Socket.write accordingly.

Cross-platform support for `getpeername()` has been implemented.

Windows support for the new `std.x.os.Socket` has been implemented. To
accomplish this, a full refactor of `std.os.windows.ws2_32` has been
done to supply any missing definitions and constants based on
auto-generated Windows syscall bindings by @marler8997.

`std.x.net.TCP.Listener.setQuickACK` has been moved to
`std.x.net.TCP.Client.setQuickACK`.

Windows support for resolving the scope ID of an interface name
specified in an IPv6 address has been provided.

`sockaddr_storage` definitions have been provided for Windows, Linux,
and Darwin. `sockaddr_storage` is used to allocate space before any
socket addresses are queried via. calls such as accept(), getsockname(),
and getpeername().

Zig-friendly wrappers for GetQueuedCompletionStatusEx(), getpeername(),
SetConsoleCtrlHandler(), SetFileCompletionNotificationModes() syscalls
on Windows have been provided.

Socket.setOption() was provided to set the value of a socket option in
place of os.setsockopt. Socket.getOption() will be provided in a future
PR.

There is still further work to be done regarding querying socket option
values on Windows, which is to be done in a subsequent PR.
This commit is contained in:
lithdew 2021-05-08 22:44:39 +09:00
parent 9c03b39d1e
commit d7b601b35e
16 changed files with 2596 additions and 584 deletions

View File

@ -151,6 +151,7 @@ pub extern "c" fn bind(socket: fd_t, address: ?*const sockaddr, address_len: soc
pub extern "c" fn socketpair(domain: c_uint, sock_type: c_uint, protocol: c_uint, sv: *[2]fd_t) c_int; pub extern "c" fn socketpair(domain: c_uint, sock_type: c_uint, protocol: c_uint, sv: *[2]fd_t) c_int;
pub extern "c" fn listen(sockfd: fd_t, backlog: c_uint) c_int; pub extern "c" fn listen(sockfd: fd_t, backlog: c_uint) c_int;
pub extern "c" fn getsockname(sockfd: fd_t, noalias addr: *sockaddr, noalias addrlen: *socklen_t) c_int; pub extern "c" fn getsockname(sockfd: fd_t, noalias addr: *sockaddr, noalias addrlen: *socklen_t) c_int;
pub extern "c" fn getpeername(sockfd: fd_t, noalias addr: *sockaddr, noalias addrlen: *socklen_t) c_int;
pub extern "c" fn connect(sockfd: fd_t, sock_addr: *const sockaddr, addrlen: socklen_t) c_int; pub extern "c" fn connect(sockfd: fd_t, sock_addr: *const sockaddr, addrlen: socklen_t) c_int;
pub extern "c" fn accept(sockfd: fd_t, noalias addr: ?*sockaddr, noalias addrlen: ?*socklen_t) c_int; pub extern "c" fn accept(sockfd: fd_t, noalias addr: ?*sockaddr, noalias addrlen: ?*socklen_t) c_int;
pub extern "c" fn accept4(sockfd: fd_t, noalias addr: ?*sockaddr, noalias addrlen: ?*socklen_t, flags: c_uint) c_int; pub extern "c" fn accept4(sockfd: fd_t, noalias addr: ?*sockaddr, noalias addrlen: ?*socklen_t, flags: c_uint) c_int;

View File

@ -3217,6 +3217,35 @@ pub fn getsockname(sock: socket_t, addr: *sockaddr, addrlen: *socklen_t) GetSock
} }
} }
pub fn getpeername(sock: socket_t, addr: *sockaddr, addrlen: *socklen_t) GetSockNameError!void {
if (builtin.os.tag == .windows) {
const rc = windows.getpeername(sock, addr, addrlen);
if (rc == windows.ws2_32.SOCKET_ERROR) {
switch (windows.ws2_32.WSAGetLastError()) {
.WSANOTINITIALISED => unreachable,
.WSAENETDOWN => return error.NetworkSubsystemFailed,
.WSAEFAULT => unreachable, // addr or addrlen have invalid pointers or addrlen points to an incorrect value
.WSAENOTSOCK => return error.FileDescriptorNotASocket,
.WSAEINVAL => return error.SocketNotBound,
else => |err| return windows.unexpectedWSAError(err),
}
}
return;
} else {
const rc = system.getpeername(sock, addr, addrlen);
switch (errno(rc)) {
0 => return,
else => |err| return unexpectedErrno(err),
EBADF => unreachable, // always a race condition
EFAULT => unreachable,
EINVAL => unreachable, // invalid parameters
ENOTSOCK => return error.FileDescriptorNotASocket,
ENOBUFS => return error.SystemResources,
}
}
}
pub const ConnectError = error{ pub const ConnectError = error{
/// For UNIX domain sockets, which are identified by pathname: Write permission is denied on the socket /// For UNIX domain sockets, which are identified by pathname: Write permission is denied on the socket
/// file, or search permission is denied for one of the directories in the path prefix. /// file, or search permission is denied for one of the directories in the path prefix.

View File

@ -23,6 +23,13 @@ pub const sockaddr = extern struct {
family: sa_family_t, family: sa_family_t,
data: [14]u8, data: [14]u8,
}; };
pub const sockaddr_storage = extern struct {
len: u8,
family: sa_family_t,
__pad1: [6]u8,
__align: i64,
__pad2: [112]u8,
};
pub const sockaddr_in = extern struct { pub const sockaddr_in = extern struct {
len: u8 = @sizeOf(sockaddr_in), len: u8 = @sizeOf(sockaddr_in),
family: sa_family_t = AF_INET, family: sa_family_t = AF_INET,

View File

@ -1149,6 +1149,13 @@ pub const sockaddr = extern struct {
data: [14]u8, data: [14]u8,
}; };
pub const sockaddr_storage = extern struct {
family: sa_family_t,
__pad1: [6]u8,
__align: i64,
__pad2: [112]u8,
};
/// IPv4 socket address /// IPv4 socket address
pub const sockaddr_in = extern struct { pub const sockaddr_in = extern struct {
family: sa_family_t = AF_INET, family: sa_family_t = AF_INET,

View File

@ -321,3 +321,5 @@ pub const O_NOATIME = 0o1000000;
pub const O_PATH = 0o10000000; pub const O_PATH = 0o10000000;
pub const O_TMPFILE = 0o20200000; pub const O_TMPFILE = 0o20200000;
pub const O_NDELAY = O_NONBLOCK; pub const O_NDELAY = O_NONBLOCK;
pub const IFNAMESIZE = 30;

View File

@ -389,6 +389,43 @@ pub fn GetQueuedCompletionStatus(
return GetQueuedCompletionStatusResult.Normal; return GetQueuedCompletionStatusResult.Normal;
} }
pub const GetQueuedCompletionStatusError = error{
Aborted,
Cancelled,
EOF,
Timeout,
} || std.os.UnexpectedError;
pub fn GetQueuedCompletionStatusEx(
completion_port: HANDLE,
completion_port_entries: []OVERLAPPED_ENTRY,
timeout_ms: ?DWORD,
alertable: bool,
) GetQueuedCompletionStatusError!u32 {
var num_entries_removed: u32 = 0;
const success = kernel32.GetQueuedCompletionStatusEx(
completion_port,
completion_port_entries.ptr,
@intCast(ULONG, completion_port_entries.len),
&num_entries_removed,
timeout_ms orelse INFINITE,
@boolToInt(alertable),
);
if (success == FALSE) {
return switch (kernel32.GetLastError()) {
.ABANDONED_WAIT_0 => error.Aborted,
.OPERATION_ABORTED => error.Cancelled,
.HANDLE_EOF => error.EOF,
.IMEOUT => error.Timeout,
else => |err| unexpectedError(err),
};
}
return num_entries_removed;
}
pub fn CloseHandle(hObject: HANDLE) void { pub fn CloseHandle(hObject: HANDLE) void {
assert(ntdll.NtClose(hObject) == .SUCCESS); assert(ntdll.NtClose(hObject) == .SUCCESS);
} }
@ -1291,6 +1328,10 @@ pub fn getsockname(s: ws2_32.SOCKET, name: *ws2_32.sockaddr, namelen: *ws2_32.so
return ws2_32.getsockname(s, name, @ptrCast(*i32, namelen)); return ws2_32.getsockname(s, name, @ptrCast(*i32, namelen));
} }
pub fn getpeername(s: ws2_32.SOCKET, name: *ws2_32.sockaddr, namelen: *ws2_32.socklen_t) i32 {
return ws2_32.getpeername(s, name, @ptrCast(*i32, namelen));
}
pub fn sendmsg( pub fn sendmsg(
s: ws2_32.SOCKET, s: ws2_32.SOCKET,
msg: *const ws2_32.WSAMSG, msg: *const ws2_32.WSAMSG,
@ -1404,6 +1445,28 @@ pub fn SetConsoleTextAttribute(hConsoleOutput: HANDLE, wAttributes: WORD) SetCon
} }
} }
pub fn SetConsoleCtrlHandler(handler_routine: ?HANDLER_ROUTINE, add: bool) !void {
const success = kernel32.SetConsoleCtrlHandler(
handler_routine,
if (add) TRUE else FALSE,
);
if (success == FALSE) {
return switch (kernel32.GetLastError()) {
else => |err| unexpectedError(err),
};
}
}
pub fn SetFileCompletionNotificationModes(handle: HANDLE, flags: UCHAR) !void {
const success = kernel32.SetFileCompletionNotificationModes(handle, flags);
if (success == FALSE) {
return switch (kernel32.GetLastError()) {
else => |err| unexpectedError(err),
};
}
}
pub const GetEnvironmentStringsError = error{OutOfMemory}; pub const GetEnvironmentStringsError = error{OutOfMemory};
pub fn GetEnvironmentStringsW() GetEnvironmentStringsError![*:0]u16 { pub fn GetEnvironmentStringsW() GetEnvironmentStringsError![*:0]u16 {

View File

@ -1619,10 +1619,6 @@ pub const MOUNTMGR_MOUNT_POINTS = extern struct {
}; };
pub const IOCTL_MOUNTMGR_QUERY_POINTS: ULONG = 0x6d0008; pub const IOCTL_MOUNTMGR_QUERY_POINTS: ULONG = 0x6d0008;
pub const SD_RECEIVE = 0;
pub const SD_SEND = 1;
pub const SD_BOTH = 2;
pub const OBJECT_INFORMATION_CLASS = extern enum { pub const OBJECT_INFORMATION_CLASS = extern enum {
ObjectBasicInformation = 0, ObjectBasicInformation = 0,
ObjectNameInformation = 1, ObjectNameInformation = 1,
@ -1642,3 +1638,14 @@ pub const SRWLOCK = usize;
pub const SRWLOCK_INIT: SRWLOCK = 0; pub const SRWLOCK_INIT: SRWLOCK = 0;
pub const CONDITION_VARIABLE = usize; pub const CONDITION_VARIABLE = usize;
pub const CONDITION_VARIABLE_INIT: CONDITION_VARIABLE = 0; pub const CONDITION_VARIABLE_INIT: CONDITION_VARIABLE = 0;
pub const FILE_SKIP_COMPLETION_PORT_ON_SUCCESS = 0x1;
pub const FILE_SKIP_SET_EVENT_ON_HANDLE = 0x2;
pub const CTRL_C_EVENT: DWORD = 0;
pub const CTRL_BREAK_EVENT: DWORD = 1;
pub const CTRL_CLOSE_EVENT: DWORD = 2;
pub const CTRL_LOGOFF_EVENT: DWORD = 5;
pub const CTRL_SHUTDOWN_EVENT: DWORD = 6;
pub const HANDLER_ROUTINE = fn (dwCtrlType: DWORD) callconv(.C) BOOL;

View File

@ -140,6 +140,14 @@ pub extern "kernel32" fn GetOverlappedResult(hFile: HANDLE, lpOverlapped: *OVERL
pub extern "kernel32" fn GetProcessHeap() callconv(WINAPI) ?HANDLE; pub extern "kernel32" fn GetProcessHeap() callconv(WINAPI) ?HANDLE;
pub extern "kernel32" fn GetQueuedCompletionStatus(CompletionPort: HANDLE, lpNumberOfBytesTransferred: LPDWORD, lpCompletionKey: *ULONG_PTR, lpOverlapped: *?*OVERLAPPED, dwMilliseconds: DWORD) callconv(WINAPI) BOOL; pub extern "kernel32" fn GetQueuedCompletionStatus(CompletionPort: HANDLE, lpNumberOfBytesTransferred: LPDWORD, lpCompletionKey: *ULONG_PTR, lpOverlapped: *?*OVERLAPPED, dwMilliseconds: DWORD) callconv(WINAPI) BOOL;
pub extern "kernel32" fn GetQueuedCompletionStatusEx(
CompletionPort: HANDLE,
lpCompletionPortEntries: [*]OVERLAPPED_ENTRY,
ulCount: ULONG,
ulNumEntriesRemoved: *ULONG,
dwMilliseconds: DWORD,
fAlertable: BOOL,
) callconv(WINAPI) BOOL;
pub extern "kernel32" fn GetSystemInfo(lpSystemInfo: *SYSTEM_INFO) callconv(WINAPI) void; pub extern "kernel32" fn GetSystemInfo(lpSystemInfo: *SYSTEM_INFO) callconv(WINAPI) void;
pub extern "kernel32" fn GetSystemTimeAsFileTime(*FILETIME) callconv(WINAPI) void; pub extern "kernel32" fn GetSystemTimeAsFileTime(*FILETIME) callconv(WINAPI) void;
@ -197,6 +205,16 @@ pub extern "kernel32" fn RemoveDirectoryW(lpPathName: [*:0]const u16) callconv(W
pub extern "kernel32" fn SetConsoleTextAttribute(hConsoleOutput: HANDLE, wAttributes: WORD) callconv(WINAPI) BOOL; pub extern "kernel32" fn SetConsoleTextAttribute(hConsoleOutput: HANDLE, wAttributes: WORD) callconv(WINAPI) BOOL;
pub extern "kernel32" fn SetConsoleCtrlHandler(
HandlerRoutine: ?HANDLER_ROUTINE,
Add: BOOL,
) callconv(WINAPI) BOOL;
pub extern "kernel32" fn SetFileCompletionNotificationModes(
FileHandle: HANDLE,
Flags: UCHAR,
) callconv(WINAPI) BOOL;
pub extern "kernel32" fn SetFilePointerEx( pub extern "kernel32" fn SetFilePointerEx(
in_fFile: HANDLE, in_fFile: HANDLE,
in_liDistanceToMove: LARGE_INTEGER, in_liDistanceToMove: LARGE_INTEGER,

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,7 @@
const std = @import("std.zig"); const std = @import("std.zig");
pub const os = struct { pub const os = struct {
pub const Socket = @import("x/os/Socket.zig"); pub const Socket = @import("x/os/socket.zig").Socket;
pub usingnamespace @import("x/os/net.zig"); pub usingnamespace @import("x/os/net.zig");
}; };

View File

@ -89,41 +89,30 @@ pub const Client = struct {
return self.socket.connect(address.into()); return self.socket.connect(address.into());
} }
/// Read data from the socket into the buffer provided. It returns the
/// number of bytes read into the buffer provided.
pub fn read(self: Client, buf: []u8) !usize {
return self.socket.read(buf);
}
/// Read data from the socket into the buffer provided with a set of flags /// Read data from the socket into the buffer provided with a set of flags
/// specified. It returns the number of bytes read into the buffer provided. /// specified. It returns the number of bytes read into the buffer provided.
pub fn recv(self: Client, buf: []u8, flags: u32) !usize { pub fn read(self: Client, buf: []u8, flags: u32) !usize {
return self.socket.recv(buf, flags); return self.socket.read(buf, flags);
}
/// Write a buffer of data provided to the socket. It returns the number
/// of bytes that are written to the socket.
pub fn write(self: Client, buf: []const u8) !usize {
return self.socket.write(buf);
}
/// Writes multiple I/O vectors to the socket. It returns the number
/// of bytes that are written to the socket.
pub fn writev(self: Client, buffers: []const os.iovec_const) !usize {
return self.socket.writev(buffers);
} }
/// Write a buffer of data provided to the socket with a set of flags specified. /// Write a buffer of data provided to the socket with a set of flags specified.
/// It returns the number of bytes that are written to the socket. /// It returns the number of bytes that are written to the socket.
pub fn send(self: Client, buf: []const u8, flags: u32) !usize { pub fn write(self: Client, buf: []const u8, flags: u32) !usize {
return self.socket.send(buf, flags); return self.socket.write(buf, flags);
} }
/// Writes multiple I/O vectors with a prepended message header to the socket /// Writes multiple I/O vectors with a prepended message header to the socket
/// with a set of flags specified. It returns the number of bytes that are /// with a set of flags specified. It returns the number of bytes that are
/// written to the socket. /// written to the socket.
pub fn sendmsg(self: Client, msg: os.msghdr_const, flags: u32) !usize { pub fn writeVectorized(self: Client, msg: os.msghdr_const, flags: u32) !usize {
return self.socket.sendmsg(msg, flags); return self.socket.writeVectorized(msg, flags);
}
/// Read multiple I/O vectors with a prepended message header from the socket
/// with a set of flags specified. It returns the number of bytes that were
/// read into the buffer provided.
pub fn readVectorized(self: cLIENT, msg: *os.msghdr, flags: u32) !usize {
return self.socket.readVectorized(msg, flags);
} }
/// Query and return the latest cached error on the client's underlying socket. /// Query and return the latest cached error on the client's underlying socket.
@ -146,12 +135,41 @@ pub const Client = struct {
return ip.Address.from(try self.socket.getLocalAddress()); return ip.Address.from(try self.socket.getLocalAddress());
} }
/// Query the address that the socket is connected to.
pub fn getRemoteAddress(self: Client) !ip.Address {
return ip.Address.from(try self.socket.getRemoteAddress());
}
/// Have close() or shutdown() syscalls block until all queued messages in the client have been successfully
/// sent, or if the timeout specified in seconds has been reached. It returns `error.UnsupportedSocketOption`
/// if the host does not support the option for a socket to linger around up until a timeout specified in
/// seconds.
pub fn setLinger(self: Client, timeout_seconds: ?u16) !void {
return self.socket.setLinger(timeout_seconds);
}
/// Have keep-alive messages be sent periodically. The timing in which keep-alive messages are sent are
/// dependant on operating system settings. It returns `error.UnsupportedSocketOption` if the host does
/// not support periodically sending keep-alive messages on connection-oriented sockets.
pub fn setKeepAlive(self: Client, enabled: bool) !void {
return self.socket.setKeepAlive(enabled);
}
/// Disable Nagle's algorithm on a TCP socket. It returns `error.UnsupportedSocketOption` if /// Disable Nagle's algorithm on a TCP socket. It returns `error.UnsupportedSocketOption` if
/// the host does not support sockets disabling Nagle's algorithm. /// the host does not support sockets disabling Nagle's algorithm.
pub fn setNoDelay(self: Client, enabled: bool) !void { pub fn setNoDelay(self: Client, enabled: bool) !void {
if (comptime @hasDecl(os, "TCP_NODELAY")) { if (comptime @hasDecl(os, "TCP_NODELAY")) {
const bytes = mem.asBytes(&@as(usize, @boolToInt(enabled))); const bytes = mem.asBytes(&@as(usize, @boolToInt(enabled)));
return os.setsockopt(self.socket.fd, os.IPPROTO_TCP, os.TCP_NODELAY, bytes); return self.socket.setOption(os.IPPROTO_TCP, os.TCP_NODELAY, bytes);
}
return error.UnsupportedSocketOption;
}
/// Enables TCP Quick ACK on a TCP socket to immediately send rather than delay ACKs when necessary. It returns
/// `error.UnsupportedSocketOption` if the host does not support TCP Quick ACK.
pub fn setQuickACK(self: Client, enabled: bool) !void {
if (comptime @hasDecl(os, "TCP_QUICKACK")) {
return self.socket.setOption(os.IPPROTO_TCP, os.TCP_QUICKACK, mem.asBytes(&@as(u32, @boolToInt(enabled))));
} }
return error.UnsupportedSocketOption; return error.UnsupportedSocketOption;
} }
@ -169,7 +187,7 @@ pub const Client = struct {
/// Set a timeout on the socket that is to occur if no messages are successfully written /// Set a timeout on the socket that is to occur if no messages are successfully written
/// to its bound destination after a specified number of milliseconds. A subsequent write /// to its bound destination after a specified number of milliseconds. A subsequent write
/// to the socket will thereafter return `error.WouldBlock` should the timeout be exceeded. /// to the socket will thereafter return `error.WouldBlock` should the timeout be exceeded.
pub fn setWriteTimeout(self: Client, milliseconds: usize) !void { pub fn setWriteTimeout(self: Client, milliseconds: u32) !void {
return self.socket.setWriteTimeout(milliseconds); return self.socket.setWriteTimeout(milliseconds);
} }
@ -177,7 +195,7 @@ pub const Client = struct {
/// from its bound destination after a specified number of milliseconds. A subsequent /// from its bound destination after a specified number of milliseconds. A subsequent
/// read from the socket will thereafter return `error.WouldBlock` should the timeout be /// read from the socket will thereafter return `error.WouldBlock` should the timeout be
/// exceeded. /// exceeded.
pub fn setReadTimeout(self: Client, milliseconds: usize) !void { pub fn setReadTimeout(self: Client, milliseconds: u32) !void {
return self.socket.setReadTimeout(milliseconds); return self.socket.setReadTimeout(milliseconds);
} }
}; };
@ -251,16 +269,7 @@ pub const Listener = struct {
/// support TCP Fast Open. /// support TCP Fast Open.
pub fn setFastOpen(self: Listener, enabled: bool) !void { pub fn setFastOpen(self: Listener, enabled: bool) !void {
if (comptime @hasDecl(os, "TCP_FASTOPEN")) { if (comptime @hasDecl(os, "TCP_FASTOPEN")) {
return os.setsockopt(self.socket.fd, os.IPPROTO_TCP, os.TCP_FASTOPEN, mem.asBytes(&@as(usize, @boolToInt(enabled)))); return self.socket.setOption(os.IPPROTO_TCP, os.TCP_FASTOPEN, mem.asBytes(&@as(u32, @boolToInt(enabled))));
}
return error.UnsupportedSocketOption;
}
/// Enables TCP Quick ACK on a TCP socket to immediately send rather than delay ACKs when necessary. It returns
/// `error.UnsupportedSocketOption` if the host does not support TCP Quick ACK.
pub fn setQuickACK(self: Listener, enabled: bool) !void {
if (comptime @hasDecl(os, "TCP_QUICKACK")) {
return os.setsockopt(self.socket.fd, os.IPPROTO_TCP, os.TCP_QUICKACK, mem.asBytes(&@as(usize, @boolToInt(enabled))));
} }
return error.UnsupportedSocketOption; return error.UnsupportedSocketOption;
} }
@ -322,7 +331,7 @@ test "tcp/client: set read timeout of 1 millisecond on blocking client" {
defer conn.deinit(); defer conn.deinit();
var buf: [1]u8 = undefined; var buf: [1]u8 = undefined;
try testing.expectError(error.WouldBlock, client.read(&buf)); try testing.expectError(error.WouldBlock, client.reader(0).read(&buf));
} }
test "tcp/listener: bind to unspecified ipv4 address" { test "tcp/listener: bind to unspecified ipv4 address" {

View File

@ -1,295 +0,0 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2015-2021 Zig Contributors
// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
// The MIT license requires this copyright notice to be included in all copies
// and substantial portions of the software.
const std = @import("../../std.zig");
const net = @import("net.zig");
const os = std.os;
const fmt = std.fmt;
const mem = std.mem;
const time = std.time;
/// A generic socket abstraction.
const Socket = @This();
/// A socket-address pair.
pub const Connection = struct {
socket: Socket,
address: Socket.Address,
/// Enclose a socket and address into a socket-address pair.
pub fn from(socket: Socket, address: Socket.Address) Socket.Connection {
return .{ .socket = socket, .address = address };
}
};
/// A generic socket address abstraction. It is safe to directly access and modify
/// the fields of a `Socket.Address`.
pub const Address = union(enum) {
ipv4: net.IPv4.Address,
ipv6: net.IPv6.Address,
/// Instantiate a new address with a IPv4 host and port.
pub fn initIPv4(host: net.IPv4, port: u16) Socket.Address {
return .{ .ipv4 = .{ .host = host, .port = port } };
}
/// Instantiate a new address with a IPv6 host and port.
pub fn initIPv6(host: net.IPv6, port: u16) Socket.Address {
return .{ .ipv6 = .{ .host = host, .port = port } };
}
/// Parses a `sockaddr` into a generic socket address.
pub fn fromNative(address: *align(4) const os.sockaddr) Socket.Address {
switch (address.family) {
os.AF_INET => {
const info = @ptrCast(*const os.sockaddr_in, address);
const host = net.IPv4{ .octets = @bitCast([4]u8, info.addr) };
const port = mem.bigToNative(u16, info.port);
return Socket.Address.initIPv4(host, port);
},
os.AF_INET6 => {
const info = @ptrCast(*const os.sockaddr_in6, address);
const host = net.IPv6{ .octets = info.addr, .scope_id = info.scope_id };
const port = mem.bigToNative(u16, info.port);
return Socket.Address.initIPv6(host, port);
},
else => unreachable,
}
}
/// Encodes a generic socket address into an extern union that may be reliably
/// casted into a `sockaddr` which may be passed into socket syscalls.
pub fn toNative(self: Socket.Address) extern union {
ipv4: os.sockaddr_in,
ipv6: os.sockaddr_in6,
} {
return switch (self) {
.ipv4 => |address| .{
.ipv4 = .{
.addr = @bitCast(u32, address.host.octets),
.port = mem.nativeToBig(u16, address.port),
},
},
.ipv6 => |address| .{
.ipv6 = .{
.addr = address.host.octets,
.port = mem.nativeToBig(u16, address.port),
.scope_id = address.host.scope_id,
.flowinfo = 0,
},
},
};
}
/// Returns the number of bytes that make up the `sockaddr` equivalent to the address.
pub fn getNativeSize(self: Socket.Address) u32 {
return switch (self) {
.ipv4 => @sizeOf(os.sockaddr_in),
.ipv6 => @sizeOf(os.sockaddr_in6),
};
}
/// Implements the `std.fmt.format` API.
pub fn format(
self: Socket.Address,
comptime layout: []const u8,
opts: fmt.FormatOptions,
writer: anytype,
) !void {
switch (self) {
.ipv4 => |address| try fmt.format(writer, "{}:{}", .{ address.host, address.port }),
.ipv6 => |address| try fmt.format(writer, "{}:{}", .{ address.host, address.port }),
}
}
};
/// The underlying handle of a socket.
fd: os.socket_t,
/// Open a new socket.
pub fn init(domain: u32, socket_type: u32, protocol: u32) !Socket {
return Socket{ .fd = try os.socket(domain, socket_type, protocol) };
}
/// Enclose a socket abstraction over an existing socket file descriptor.
pub fn from(fd: os.socket_t) Socket {
return Socket{ .fd = fd };
}
/// Closes the socket.
pub fn deinit(self: Socket) void {
os.closeSocket(self.fd);
}
/// Shutdown either the read side, write side, or all side of the socket.
pub fn shutdown(self: Socket, how: os.ShutdownHow) !void {
return os.shutdown(self.fd, how);
}
/// Binds the socket to an address.
pub fn bind(self: Socket, address: Socket.Address) !void {
return os.bind(self.fd, @ptrCast(*const os.sockaddr, &address.toNative()), address.getNativeSize());
}
/// Start listening for incoming connections on the socket.
pub fn listen(self: Socket, max_backlog_size: u31) !void {
return os.listen(self.fd, max_backlog_size);
}
/// Have the socket attempt to the connect to an address.
pub fn connect(self: Socket, address: Socket.Address) !void {
return os.connect(self.fd, @ptrCast(*const os.sockaddr, &address.toNative()), address.getNativeSize());
}
/// Accept a pending incoming connection queued to the kernel backlog
/// of the socket.
pub fn accept(self: Socket, flags: u32) !Socket.Connection {
var address: os.sockaddr = undefined;
var address_len: u32 = @sizeOf(os.sockaddr);
const socket = Socket{ .fd = try os.accept(self.fd, &address, &address_len, flags) };
const socket_address = Socket.Address.fromNative(@alignCast(4, &address));
return Socket.Connection.from(socket, socket_address);
}
/// Read data from the socket into the buffer provided. It returns the
/// number of bytes read into the buffer provided.
pub fn read(self: Socket, buf: []u8) !usize {
return os.read(self.fd, buf);
}
/// Read data from the socket into the buffer provided with a set of flags
/// specified. It returns the number of bytes read into the buffer provided.
pub fn recv(self: Socket, buf: []u8, flags: u32) !usize {
return os.recv(self.fd, buf, flags);
}
/// Write a buffer of data provided to the socket. It returns the number
/// of bytes that are written to the socket.
pub fn write(self: Socket, buf: []const u8) !usize {
return os.write(self.fd, buf);
}
/// Writes multiple I/O vectors to the socket. It returns the number
/// of bytes that are written to the socket.
pub fn writev(self: Socket, buffers: []const os.iovec_const) !usize {
return os.writev(self.fd, buffers);
}
/// Write a buffer of data provided to the socket with a set of flags specified.
/// It returns the number of bytes that are written to the socket.
pub fn send(self: Socket, buf: []const u8, flags: u32) !usize {
return os.send(self.fd, buf, flags);
}
/// Writes multiple I/O vectors with a prepended message header to the socket
/// with a set of flags specified. It returns the number of bytes that are
/// written to the socket.
pub fn sendmsg(self: Socket, msg: os.msghdr_const, flags: u32) !usize {
return os.sendmsg(self.fd, msg, flags);
}
/// Query the address that the socket is locally bounded to.
pub fn getLocalAddress(self: Socket) !Socket.Address {
var address: os.sockaddr = undefined;
var address_len: u32 = @sizeOf(os.sockaddr);
try os.getsockname(self.fd, &address, &address_len);
return Socket.Address.fromNative(@alignCast(4, &address));
}
/// Query and return the latest cached error on the socket.
pub fn getError(self: Socket) !void {
return os.getsockoptError(self.fd);
}
/// Query the read buffer size of the socket.
pub fn getReadBufferSize(self: Socket) !u32 {
var value: u32 = undefined;
var value_len: u32 = @sizeOf(u32);
const rc = os.system.getsockopt(self.fd, os.SOL_SOCKET, os.SO_RCVBUF, mem.asBytes(&value), &value_len);
return switch (os.errno(rc)) {
0 => value,
os.EBADF => error.BadFileDescriptor,
os.EFAULT => error.InvalidAddressSpace,
os.EINVAL => error.InvalidSocketOption,
os.ENOPROTOOPT => error.UnknownSocketOption,
os.ENOTSOCK => error.NotASocket,
else => |err| os.unexpectedErrno(err),
};
}
/// Query the write buffer size of the socket.
pub fn getWriteBufferSize(self: Socket) !u32 {
var value: u32 = undefined;
var value_len: u32 = @sizeOf(u32);
const rc = os.system.getsockopt(self.fd, os.SOL_SOCKET, os.SO_SNDBUF, mem.asBytes(&value), &value_len);
return switch (os.errno(rc)) {
0 => value,
os.EBADF => error.BadFileDescriptor,
os.EFAULT => error.InvalidAddressSpace,
os.EINVAL => error.InvalidSocketOption,
os.ENOPROTOOPT => error.UnknownSocketOption,
os.ENOTSOCK => error.NotASocket,
else => |err| os.unexpectedErrno(err),
};
}
/// Allow multiple sockets on the same host to listen on the same address. It returns `error.UnsupportedSocketOption` if
/// the host does not support sockets listening the same address.
pub fn setReuseAddress(self: Socket, enabled: bool) !void {
if (comptime @hasDecl(os, "SO_REUSEADDR")) {
return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_REUSEADDR, mem.asBytes(&@as(usize, @boolToInt(enabled))));
}
return error.UnsupportedSocketOption;
}
/// Allow multiple sockets on the same host to listen on the same port. It returns `error.UnsupportedSocketOption` if
/// the host does not supports sockets listening on the same port.
pub fn setReusePort(self: Socket, enabled: bool) !void {
if (comptime @hasDecl(os, "SO_REUSEPORT")) {
return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_REUSEPORT, mem.asBytes(&@as(usize, @boolToInt(enabled))));
}
return error.UnsupportedSocketOption;
}
/// Set the write buffer size of the socket.
pub fn setWriteBufferSize(self: Socket, size: u32) !void {
return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_SNDBUF, mem.asBytes(&size));
}
/// Set the read buffer size of the socket.
pub fn setReadBufferSize(self: Socket, size: u32) !void {
return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_RCVBUF, mem.asBytes(&size));
}
/// Set a timeout on the socket that is to occur if no messages are successfully written
/// to its bound destination after a specified number of milliseconds. A subsequent write
/// to the socket will thereafter return `error.WouldBlock` should the timeout be exceeded.
pub fn setWriteTimeout(self: Socket, milliseconds: usize) !void {
const timeout = os.timeval{
.tv_sec = @intCast(i32, milliseconds / time.ms_per_s),
.tv_usec = @intCast(i32, (milliseconds % time.ms_per_s) * time.us_per_ms),
};
return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_SNDTIMEO, mem.asBytes(&timeout));
}
/// Set a timeout on the socket that is to occur if no messages are successfully read
/// from its bound destination after a specified number of milliseconds. A subsequent
/// read from the socket will thereafter return `error.WouldBlock` should the timeout be
/// exceeded.
pub fn setReadTimeout(self: Socket, milliseconds: usize) !void {
const timeout = os.timeval{
.tv_sec = @intCast(i32, milliseconds / time.ms_per_s),
.tv_usec = @intCast(i32, (milliseconds % time.ms_per_s) * time.us_per_ms),
};
return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_RCVTIMEO, mem.asBytes(&timeout));
}

View File

@ -20,6 +20,14 @@ pub fn resolveScopeID(name: []const u8) !u32 {
if (comptime @hasDecl(os, "IFNAMESIZE")) { if (comptime @hasDecl(os, "IFNAMESIZE")) {
if (name.len >= os.IFNAMESIZE - 1) return error.NameTooLong; if (name.len >= os.IFNAMESIZE - 1) return error.NameTooLong;
if (comptime builtin.os.tag == .windows) {
var interface_name: [os.IFNAMESIZE]u8 = undefined;
mem.copy(u8, &interface_name, name);
interface_name[name.len] = 0;
return os.windows.ws2_32.if_nametoindex(@ptrCast([*:0]const u8, &interface_name));
}
const fd = try os.socket(os.AF_UNIX, os.SOCK_DGRAM, 0); const fd = try os.socket(os.AF_UNIX, os.SOCK_DGRAM, 0);
defer os.closeSocket(fd); defer os.closeSocket(fd);
@ -31,6 +39,7 @@ pub fn resolveScopeID(name: []const u8) !u32 {
return @bitCast(u32, f.ifru.ivalue); return @bitCast(u32, f.ifru.ivalue);
} }
return error.Unsupported; return error.Unsupported;
} }

156
lib/std/x/os/socket.zig Normal file
View File

@ -0,0 +1,156 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2015-2021 Zig Contributors
// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
// The MIT license requires this copyright notice to be included in all copies
// and substantial portions of the software.
const std = @import("../../std.zig");
const net = @import("net.zig");
const io = std.io;
const os = std.os;
const fmt = std.fmt;
const mem = std.mem;
const time = std.time;
const builtin = std.builtin;
/// Import in a `Socket` abstraction depending on the platform we are compiling against.
pub usingnamespace switch (builtin.os.tag) {
.windows => @import("socket_windows.zig"),
else => @import("socket_posix.zig"),
};
/// A common subset of shared structs across cross-platform abstractions over socket syscalls.
pub fn Mixin(comptime Self: type) type {
return struct {
/// A socket-address pair.
pub const Connection = struct {
socket: Self,
address: Self.Address,
/// Enclose a socket and address into a socket-address pair.
pub fn from(socket: Self, address: Self.Address) Self.Connection {
return .{ .socket = socket, .address = address };
}
};
/// A generic socket address abstraction. It is safe to directly access and modify
/// the fields of a `Self.Address`.
pub const Address = union(enum) {
ipv4: net.IPv4.Address,
ipv6: net.IPv6.Address,
/// Instantiate a new address with a IPv4 host and port.
pub fn initIPv4(host: net.IPv4, port: u16) Self.Address {
return .{ .ipv4 = .{ .host = host, .port = port } };
}
/// Instantiate a new address with a IPv6 host and port.
pub fn initIPv6(host: net.IPv6, port: u16) Self.Address {
return .{ .ipv6 = .{ .host = host, .port = port } };
}
/// Parses a `sockaddr` into a generic socket address.
pub fn fromNative(address: *align(4) const os.sockaddr) Self.Address {
switch (address.family) {
os.AF_INET => {
const info = @ptrCast(*const os.sockaddr_in, address);
const host = net.IPv4{ .octets = @bitCast([4]u8, info.addr) };
const port = mem.bigToNative(u16, info.port);
return Self.Address.initIPv4(host, port);
},
os.AF_INET6 => {
const info = @ptrCast(*const os.sockaddr_in6, address);
const host = net.IPv6{ .octets = info.addr, .scope_id = info.scope_id };
const port = mem.bigToNative(u16, info.port);
return Self.Address.initIPv6(host, port);
},
else => unreachable,
}
}
/// Encodes a generic socket address into an extern union that may be reliably
/// casted into a `sockaddr` which may be passed into socket syscalls.
pub fn toNative(self: Self.Address) extern union {
ipv4: os.sockaddr_in,
ipv6: os.sockaddr_in6,
} {
return switch (self) {
.ipv4 => |address| .{
.ipv4 = .{
.addr = @bitCast(u32, address.host.octets),
.port = mem.nativeToBig(u16, address.port),
},
},
.ipv6 => |address| .{
.ipv6 = .{
.addr = address.host.octets,
.port = mem.nativeToBig(u16, address.port),
.scope_id = address.host.scope_id,
.flowinfo = 0,
},
},
};
}
/// Returns the number of bytes that make up the `sockaddr` equivalent to the address.
pub fn getNativeSize(self: Self.Address) u32 {
return switch (self) {
.ipv4 => @sizeOf(os.sockaddr_in),
.ipv6 => @sizeOf(os.sockaddr_in6),
};
}
/// Implements the `std.fmt.format` API.
pub fn format(
self: Self.Address,
comptime layout: []const u8,
opts: fmt.FormatOptions,
writer: anytype,
) !void {
switch (self) {
.ipv4 => |address| try fmt.format(writer, "{}:{}", .{ address.host, address.port }),
.ipv6 => |address| try fmt.format(writer, "{}:{}", .{ address.host, address.port }),
}
}
};
/// Implements `std.io.Reader`.
pub const Reader = struct {
socket: Self,
flags: u32,
/// Implements `readFn` for `std.io.Reader`.
pub fn read(self: Self.Reader, buffer: []u8) !usize {
return self.socket.read(buffer, self.flags);
}
};
/// Implements `std.io.Writer`.
pub const Writer = struct {
socket: Self,
flags: u32,
/// Implements `writeFn` for `std.io.Writer`.
pub fn write(self: Self.Writer, buffer: []const u8) !usize {
return self.socket.write(buffer, self.flags);
}
};
/// Extracts the error set of a function.
/// TODO: remove after Socket.{read, write} error unions are well-defined across different platforms
fn ErrorSetOf(comptime Function: anytype) type {
return @typeInfo(@typeInfo(@TypeOf(Function)).Fn.return_type.?).ErrorUnion.error_set;
}
/// Wrap `Socket` into `std.io.Reader`.
pub fn reader(self: Self, flags: u32) io.Reader(Self.Reader, ErrorSetOf(Self.Reader.read), Self.Reader.read) {
return .{ .context = .{ .socket = self, .flags = flags } };
}
/// Wrap `Socket` into `std.io.Writer`.
pub fn writer(self: Self, flags: u32) io.Writer(Self.Writer, ErrorSetOf(Self.Writer.write), Self.Writer.write) {
return .{ .context = .{ .socket = self, .flags = flags } };
}
};
}

View File

@ -0,0 +1,235 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2015-2021 Zig Contributors
// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
// The MIT license requires this copyright notice to be included in all copies
// and substantial portions of the software.
const std = @import("../../std.zig");
const os = std.os;
const mem = std.mem;
const time = std.time;
pub const Socket = struct {
/// Import in `Socket.Address` and `Socket.Connection`.
pub usingnamespace @import("socket.zig").Mixin(Socket);
/// The underlying handle of a socket.
fd: os.socket_t,
/// Open a new socket.
pub fn init(domain: u32, socket_type: u32, protocol: u32) !Socket {
return Socket{ .fd = try os.socket(domain, socket_type, protocol) };
}
/// Enclose a socket abstraction over an existing socket file descriptor.
pub fn from(fd: os.socket_t) Socket {
return Socket{ .fd = fd };
}
/// Closes the socket.
pub fn deinit(self: Socket) void {
os.closeSocket(self.fd);
}
/// Shutdown either the read side, write side, or all side of the socket.
pub fn shutdown(self: Socket, how: os.ShutdownHow) !void {
return os.shutdown(self.fd, how);
}
/// Binds the socket to an address.
pub fn bind(self: Socket, address: Socket.Address) !void {
return os.bind(self.fd, @ptrCast(*const os.sockaddr, &address.toNative()), address.getNativeSize());
}
/// Start listening for incoming connections on the socket.
pub fn listen(self: Socket, max_backlog_size: u31) !void {
return os.listen(self.fd, max_backlog_size);
}
/// Have the socket attempt to the connect to an address.
pub fn connect(self: Socket, address: Socket.Address) !void {
return os.connect(self.fd, @ptrCast(*const os.sockaddr, &address.toNative()), address.getNativeSize());
}
/// Accept a pending incoming connection queued to the kernel backlog
/// of the socket.
pub fn accept(self: Socket, flags: u32) !Socket.Connection {
var address: os.sockaddr = undefined;
var address_len: u32 = @sizeOf(os.sockaddr);
const socket = Socket{ .fd = try os.accept(self.fd, &address, &address_len, flags) };
const socket_address = Socket.Address.fromNative(@alignCast(4, &address));
return Socket.Connection.from(socket, socket_address);
}
/// Read data from the socket into the buffer provided with a set of flags
/// specified. It returns the number of bytes read into the buffer provided.
pub fn read(self: Socket, buf: []u8, flags: u32) !usize {
return os.recv(self.fd, buf, flags);
}
/// Write a buffer of data provided to the socket with a set of flags specified.
/// It returns the number of bytes that are written to the socket.
pub fn write(self: Socket, buf: []const u8, flags: u32) !usize {
return os.send(self.fd, buf, flags);
}
/// Writes multiple I/O vectors with a prepended message header to the socket
/// with a set of flags specified. It returns the number of bytes that are
/// written to the socket.
pub fn writeVectorized(self: Socket, msg: os.msghdr_const, flags: u32) !usize {
return os.sendmsg(self.fd, msg, flags);
}
/// Read multiple I/O vectors with a prepended message header from the socket
/// with a set of flags specified. It returns the number of bytes that were
/// read into the buffer provided.
pub fn readVectorized(self: Socket, msg: *os.msghdr, flags: u32) !usize {
return error.NotImplemented;
}
/// Query the address that the socket is locally bounded to.
pub fn getLocalAddress(self: Socket) !Socket.Address {
var address: os.sockaddr_storage = undefined;
var address_len: u32 = @sizeOf(os.sockaddr_storage);
try os.getsockname(self.fd, @ptrCast(*os.sockaddr, &address), &address_len);
return Socket.Address.fromNative(@alignCast(4, @ptrCast(*os.sockaddr, &address)));
}
/// Query the address that the socket is connected to.
pub fn getRemoteAddress(self: Socket) !Socket.Address {
var address: os.sockaddr_storage = undefined;
var address_len: u32 = @sizeOf(os.sockaddr_storage);
try os.getpeername(self.fd, @ptrCast(*os.sockaddr, &address), &address_len);
return Socket.Address.fromNative(@alignCast(4, @ptrCast(*os.sockaddr, &address)));
}
/// Query and return the latest cached error on the socket.
pub fn getError(self: Socket) !void {
return os.getsockoptError(self.fd);
}
/// Query the read buffer size of the socket.
pub fn getReadBufferSize(self: Socket) !u32 {
var value: u32 = undefined;
var value_len: u32 = @sizeOf(u32);
const rc = os.system.getsockopt(self.fd, os.SOL_SOCKET, os.SO_RCVBUF, mem.asBytes(&value), &value_len);
return switch (os.errno(rc)) {
0 => value,
os.EBADF => error.BadFileDescriptor,
os.EFAULT => error.InvalidAddressSpace,
os.EINVAL => error.InvalidSocketOption,
os.ENOPROTOOPT => error.UnknownSocketOption,
os.ENOTSOCK => error.NotASocket,
else => |err| os.unexpectedErrno(err),
};
}
/// Query the write buffer size of the socket.
pub fn getWriteBufferSize(self: Socket) !u32 {
var value: u32 = undefined;
var value_len: u32 = @sizeOf(u32);
const rc = os.system.getsockopt(self.fd, os.SOL_SOCKET, os.SO_SNDBUF, mem.asBytes(&value), &value_len);
return switch (os.errno(rc)) {
0 => value,
os.EBADF => error.BadFileDescriptor,
os.EFAULT => error.InvalidAddressSpace,
os.EINVAL => error.InvalidSocketOption,
os.ENOPROTOOPT => error.UnknownSocketOption,
os.ENOTSOCK => error.NotASocket,
else => |err| os.unexpectedErrno(err),
};
}
/// Set a socket option.
pub fn setOption(self: Socket, level: u32, name: u32, value: []const u8) !void {
return os.setsockopt(self.fd, level, name, value);
}
/// Have close() or shutdown() syscalls block until all queued messages in the socket have been successfully
/// sent, or if the timeout specified in seconds has been reached. It returns `error.UnsupportedSocketOption`
/// if the host does not support the option for a socket to linger around up until a timeout specified in
/// seconds.
pub fn setLinger(self: Socket, timeout_seconds: ?u16) !void {
if (comptime @hasDecl(os, "SO_LINGER")) {
const settings = struct {
l_onoff: c_int,
l_linger: c_int,
}{
.l_onoff = @intCast(c_int, @boolToInt(timeout_seconds != null)),
.l_linger = if (timeout_seconds) |seconds| @intCast(c_int, seconds) else 0,
};
return self.setOption(os.SOL_SOCKET, os.SO_LINGER, mem.asBytes(&settings));
}
return error.UnsupportedSocketOption;
}
/// On connection-oriented sockets, have keep-alive messages be sent periodically. The timing in which keep-alive
/// messages are sent are dependant on operating system settings. It returns `error.UnsupportedSocketOption` if
/// the host does not support periodically sending keep-alive messages on connection-oriented sockets.
pub fn setKeepAlive(self: Socket, enabled: bool) !void {
if (comptime @hasDecl(os, "SO_KEEPALIVE")) {
return self.setOption(os.SOL_SOCKET, os.SO_KEEPALIVE, mem.asBytes(&@as(u32, @boolToInt(enabled))));
}
return error.UnsupportedSocketOption;
}
/// Allow multiple sockets on the same host to listen on the same address. It returns `error.UnsupportedSocketOption` if
/// the host does not support sockets listening the same address.
pub fn setReuseAddress(self: Socket, enabled: bool) !void {
if (comptime @hasDecl(os, "SO_REUSEADDR")) {
return self.setOption(os.SOL_SOCKET, os.SO_REUSEADDR, mem.asBytes(&@as(u32, @boolToInt(enabled))));
}
return error.UnsupportedSocketOption;
}
/// Allow multiple sockets on the same host to listen on the same port. It returns `error.UnsupportedSocketOption` if
/// the host does not supports sockets listening on the same port.
pub fn setReusePort(self: Socket, enabled: bool) !void {
if (comptime @hasDecl(os, "SO_REUSEPORT")) {
return self.setOption(os.SOL_SOCKET, os.SO_REUSEPORT, mem.asBytes(&@as(u32, @boolToInt(enabled))));
}
return error.UnsupportedSocketOption;
}
/// Set the write buffer size of the socket.
pub fn setWriteBufferSize(self: Socket, size: u32) !void {
return self.setOption(os.SOL_SOCKET, os.SO_SNDBUF, mem.asBytes(&size));
}
/// Set the read buffer size of the socket.
pub fn setReadBufferSize(self: Socket, size: u32) !void {
return self.setOption(os.SOL_SOCKET, os.SO_RCVBUF, mem.asBytes(&size));
}
/// Set a timeout on the socket that is to occur if no messages are successfully written
/// to its bound destination after a specified number of milliseconds. A subsequent write
/// to the socket will thereafter return `error.WouldBlock` should the timeout be exceeded.
pub fn setWriteTimeout(self: Socket, milliseconds: usize) !void {
const timeout = os.timeval{
.tv_sec = @intCast(i32, milliseconds / time.ms_per_s),
.tv_usec = @intCast(i32, (milliseconds % time.ms_per_s) * time.us_per_ms),
};
return self.setOption(os.SOL_SOCKET, os.SO_SNDTIMEO, mem.asBytes(&timeout));
}
/// Set a timeout on the socket that is to occur if no messages are successfully read
/// from its bound destination after a specified number of milliseconds. A subsequent
/// read from the socket will thereafter return `error.WouldBlock` should the timeout be
/// exceeded.
pub fn setReadTimeout(self: Socket, milliseconds: usize) !void {
const timeout = os.timeval{
.tv_sec = @intCast(i32, milliseconds / time.ms_per_s),
.tv_usec = @intCast(i32, (milliseconds % time.ms_per_s) * time.us_per_ms),
};
return self.setOption(os.SOL_SOCKET, os.SO_RCVTIMEO, mem.asBytes(&timeout));
}
};

View File

@ -0,0 +1,392 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2015-2021 Zig Contributors
// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
// The MIT license requires this copyright notice to be included in all copies
// and substantial portions of the software.
const std = @import("../../std.zig");
const net = @import("net.zig");
const os = std.os;
const mem = std.mem;
const windows = std.os.windows;
const ws2_32 = windows.ws2_32;
pub const Socket = struct {
/// Import in `Socket.Address` and `Socket.Connection`.
pub usingnamespace @import("socket.zig").Mixin(Socket);
/// The underlying handle of a socket.
fd: os.socket_t,
/// Open a new socket.
pub fn init(domain: u32, socket_type: u32, protocol: u32) !Socket {
var filtered_socket_type = socket_type & ~@as(u32, os.SOCK_CLOEXEC);
var filtered_flags = ws2_32.WSA_FLAG_OVERLAPPED;
if (socket_type & os.SOCK_CLOEXEC != 0) {
filtered_flags |= ws2_32.WSA_FLAG_NO_HANDLE_INHERIT;
}
const fd = ws2_32.WSASocketW(
@intCast(i32, domain),
@intCast(i32, filtered_socket_type),
@intCast(i32, protocol),
null,
0,
filtered_flags,
);
if (fd == ws2_32.INVALID_SOCKET) {
return switch (ws2_32.WSAGetLastError()) {
.WSANOTINITIALISED => {
_ = try windows.WSAStartup(2, 2);
return Socket.init(domain, socket_type, protocol);
},
.WSAEAFNOSUPPORT => error.AddressFamilyNotSupported,
.WSAEMFILE => error.ProcessFdQuotaExceeded,
.WSAENOBUFS => error.SystemResources,
.WSAEPROTONOSUPPORT => error.ProtocolNotSupported,
else => |err| windows.unexpectedWSAError(err),
};
}
return Socket{ .fd = fd };
}
/// Enclose a socket abstraction over an existing socket file descriptor.
pub fn from(fd: os.socket_t) Socket {
return Socket{ .fd = fd };
}
/// Closes the socket.
pub fn deinit(self: Socket) void {
_ = ws2_32.closesocket(self.fd);
}
/// Shutdown either the read side, write side, or all side of the socket.
pub fn shutdown(self: Socket, how: os.ShutdownHow) !void {
const rc = ws2_32.shutdown(self.fd, switch (how) {
.recv => ws2_32.SD_RECEIVE,
.send => ws2_32.SD_SEND,
.both => ws2_32.SD_BOTH,
});
if (rc == ws2_32.SOCKET_ERROR) {
return switch (ws2_32.WSAGetLastError()) {
.WSAECONNABORTED => return error.ConnectionAborted,
.WSAECONNRESET => return error.ConnectionResetByPeer,
.WSAEINPROGRESS => return error.BlockingOperationInProgress,
.WSAEINVAL => unreachable,
.WSAENETDOWN => return error.NetworkSubsystemFailed,
.WSAENOTCONN => return error.SocketNotConnected,
.WSAENOTSOCK => unreachable,
.WSANOTINITIALISED => unreachable,
else => |err| return windows.unexpectedWSAError(err),
};
}
}
/// Binds the socket to an address.
pub fn bind(self: Socket, address: Socket.Address) !void {
const rc = ws2_32.bind(self.fd, @ptrCast(*const ws2_32.sockaddr, &address.toNative()), @intCast(c_int, address.getNativeSize()));
if (rc == ws2_32.SOCKET_ERROR) {
return switch (ws2_32.WSAGetLastError()) {
.WSAENETDOWN => error.NetworkSubsystemFailed,
.WSAEACCES => error.AccessDenied,
.WSAEADDRINUSE => error.AddressInUse,
.WSAEADDRNOTAVAIL => error.AddressNotAvailable,
.WSAEFAULT => error.BadAddress,
.WSAEINPROGRESS => error.WouldBlock,
.WSAEINVAL => error.AlreadyBound,
.WSAENOBUFS => error.NoEphemeralPortsAvailable,
.WSAENOTSOCK => error.NotASocket,
else => |err| windows.unexpectedWSAError(err),
};
}
}
/// Start listening for incoming connections on the socket.
pub fn listen(self: Socket, max_backlog_size: u31) !void {
const rc = ws2_32.listen(self.fd, max_backlog_size);
if (rc == ws2_32.SOCKET_ERROR) {
return switch (ws2_32.WSAGetLastError()) {
.WSAENETDOWN => error.NetworkSubsystemFailed,
.WSAEADDRINUSE => error.AddressInUse,
.WSAEISCONN => error.AlreadyConnected,
.WSAEINVAL => error.SocketNotBound,
.WSAEMFILE, .WSAENOBUFS => error.SystemResources,
.WSAENOTSOCK => error.FileDescriptorNotASocket,
.WSAEOPNOTSUPP => error.OperationNotSupported,
.WSAEINPROGRESS => error.WouldBlock,
else => |err| windows.unexpectedWSAError(err),
};
}
}
/// Have the socket attempt to the connect to an address.
pub fn connect(self: Socket, address: Socket.Address) !void {
const rc = ws2_32.connect(self.fd, @ptrCast(*const ws2_32.sockaddr, &address.toNative()), @intCast(c_int, address.getNativeSize()));
if (rc == ws2_32.SOCKET_ERROR) {
return switch (ws2_32.WSAGetLastError()) {
.WSAEADDRINUSE => error.AddressInUse,
.WSAEADDRNOTAVAIL => error.AddressNotAvailable,
.WSAECONNREFUSED => error.ConnectionRefused,
.WSAETIMEDOUT => error.ConnectionTimedOut,
.WSAEFAULT => error.BadAddress,
.WSAEINVAL => error.ListeningSocket,
.WSAEISCONN => error.AlreadyConnected,
.WSAENOTSOCK => error.NotASocket,
.WSAEACCES => error.BroadcastNotEnabled,
.WSAENOBUFS => error.SystemResources,
.WSAEAFNOSUPPORT => error.AddressFamilyNotSupported,
.WSAEINPROGRESS, .WSAEWOULDBLOCK => error.WouldBlock,
.WSAEHOSTUNREACH, .WSAENETUNREACH => error.NetworkUnreachable,
else => |err| windows.unexpectedWSAError(err),
};
}
}
/// Accept a pending incoming connection queued to the kernel backlog
/// of the socket.
pub fn accept(self: Socket, flags: u32) !Socket.Connection {
var address: ws2_32.sockaddr = undefined;
var address_len: c_int = @sizeOf(ws2_32.sockaddr);
const rc = ws2_32.accept(self.fd, &address, &address_len);
if (rc == ws2_32.INVALID_SOCKET) {
return switch (ws2_32.WSAGetLastError()) {
.WSANOTINITIALISED => unreachable,
.WSAECONNRESET => error.ConnectionResetByPeer,
.WSAEFAULT => unreachable,
.WSAEINVAL => error.SocketNotListening,
.WSAEMFILE => error.ProcessFdQuotaExceeded,
.WSAENETDOWN => error.NetworkSubsystemFailed,
.WSAENOBUFS => error.FileDescriptorNotASocket,
.WSAEOPNOTSUPP => error.OperationNotSupported,
.WSAEWOULDBLOCK => error.WouldBlock,
else => |err| windows.unexpectedWSAError(err),
};
}
const socket = Socket.from(rc);
const socket_address = Socket.Address.fromNative(@alignCast(4, &address));
return Socket.Connection.from(socket, socket_address);
}
/// Read data from the socket into the buffer provided with a set of flags
/// specified. It returns the number of bytes read into the buffer provided.
pub fn read(self: Socket, buf: []u8, flags: u32) !usize {
var bufs = &[_]ws2_32.WSABUF{.{ .len = @intCast(u32, buf.len), .buf = buf.ptr }};
var flags_ = flags;
const rc = ws2_32.WSARecv(self.fd, bufs, 1, null, &flags_, null, null);
if (rc == ws2_32.SOCKET_ERROR) {
return switch (ws2_32.WSAGetLastError()) {
.WSAECONNABORTED => error.ConnectionAborted,
.WSAECONNRESET => error.ConnectionResetByPeer,
.WSAEDISCON => error.ConnectionClosedByPeer,
.WSAEFAULT => error.BadBuffer,
.WSAEINPROGRESS,
.WSAEWOULDBLOCK,
.WSA_IO_PENDING,
.WSAETIMEDOUT,
=> error.WouldBlock,
.WSAEINTR => error.Cancelled,
.WSAEINVAL => error.SocketNotBound,
.WSAEMSGSIZE => error.MessageTooLarge,
.WSAENETDOWN => error.NetworkSubsystemFailed,
.WSAENETRESET => error.NetworkReset,
.WSAENOTCONN => error.SocketNotConnected,
.WSAENOTSOCK => error.FileDescriptorNotASocket,
.WSAEOPNOTSUPP => error.OperationNotSupported,
.WSAESHUTDOWN => error.AlreadyShutdown,
.WSA_OPERATION_ABORTED => error.OperationAborted,
else => |err| windows.unexpectedWSAError(err),
};
}
return @intCast(usize, rc);
}
/// Write a buffer of data provided to the socket with a set of flags specified.
/// It returns the number of bytes that are written to the socket.
pub fn write(self: Socket, buf: []const u8, flags: u32) !usize {
var bufs = &[_]ws2_32.WSABUF{.{ .len = @intCast(u32, buf.len), .buf = buf.ptr }};
var flags_ = flags;
const rc = ws2_32.WSASend(self.fd, bufs, 1, null, &flags_, null, null);
if (rc == ws2_32.SOCKET_ERROR) {
return switch (ws2_32.WSAGetLastError()) {
.WSAECONNABORTED => error.ConnectionAborted,
.WSAECONNRESET => error.ConnectionResetByPeer,
.WSAEFAULT => error.BadBuffer,
.WSAEINPROGRESS,
.WSAEWOULDBLOCK,
.WSA_IO_PENDING,
.WSAETIMEDOUT,
=> error.WouldBlock,
.WSAEINTR => error.Cancelled,
.WSAEINVAL => error.SocketNotBound,
.WSAEMSGSIZE => error.MessageTooLarge,
.WSAENETDOWN => error.NetworkSubsystemFailed,
.WSAENETRESET => error.NetworkReset,
.WSAENOBUFS => error.BufferDeadlock,
.WSAENOTCONN => error.SocketNotConnected,
.WSAENOTSOCK => error.FileDescriptorNotASocket,
.WSAEOPNOTSUPP => error.OperationNotSupported,
.WSAESHUTDOWN => error.AlreadyShutdown,
.WSA_OPERATION_ABORTED => error.OperationAborted,
else => |err| windows.unexpectedWSAError(err),
};
}
return @intCast(usize, rc);
}
/// Writes multiple I/O vectors with a prepended message header to the socket
/// with a set of flags specified. It returns the number of bytes that are
/// written to the socket.
pub fn writeVectorized(self: Socket, msg: os.msghdr_const, flags: u32) !usize {
return error.NotImplemented;
}
/// Read multiple I/O vectors with a prepended message header from the socket
/// with a set of flags specified. It returns the number of bytes that were
/// read into the buffer provided.
pub fn readVectorized(self: Socket, msg: *os.msghdr, flags: u32) !usize {
return error.NotImplemented;
}
/// Query the address that the socket is locally bounded to.
pub fn getLocalAddress(self: Socket) !Socket.Address {
var address: ws2_32.sockaddr_storage = undefined;
var address_len: c_int = @sizeOf(ws2_32.sockaddr_storage);
const rc = ws2_32.getsockname(self.fd, @ptrCast(*ws2_32.sockaddr, &address), &address_len);
if (rc == ws2_32.SOCKET_ERROR) {
return switch (ws2_32.WSAGetLastError()) {
.WSANOTINITIALISED => unreachable,
.WSAEFAULT => unreachable,
.WSAENETDOWN => error.NetworkSubsystemFailed,
.WSAENOTSOCK => error.FileDescriptorNotASocket,
.WSAEINVAL => error.SocketNotBound,
else => |err| windows.unexpectedWSAError(err),
};
}
return Socket.Address.fromNative(@alignCast(4, @ptrCast(*os.sockaddr, &address)));
}
/// Query the address that the socket is connected to.
pub fn getRemoteAddress(self: Socket) !Socket.Address {
var address: ws2_32.sockaddr_storage = undefined;
var address_len: c_int = @sizeOf(ws2_32.sockaddr_storage);
const rc = ws2_32.getpeername(self.fd, @ptrCast(*ws2_32.sockaddr, &address), &address_len);
if (rc == ws2_32.SOCKET_ERROR) {
return switch (ws2_32.WSAGetLastError()) {
.WSANOTINITIALISED => unreachable,
.WSAEFAULT => unreachable,
.WSAENETDOWN => error.NetworkSubsystemFailed,
.WSAENOTSOCK => error.FileDescriptorNotASocket,
.WSAEINVAL => error.SocketNotBound,
else => |err| windows.unexpectedWSAError(err),
};
}
return Socket.Address.fromNative(@alignCast(4, @ptrCast(*os.sockaddr, &address)));
}
/// Query and return the latest cached error on the socket.
pub fn getError(self: Socket) !void {
return {};
}
/// Query the read buffer size of the socket.
pub fn getReadBufferSize(self: Socket) !u32 {
return 0;
}
/// Query the write buffer size of the socket.
pub fn getWriteBufferSize(self: Socket) !u32 {
return 0;
}
/// Set a socket option.
pub fn setOption(self: Socket, level: u32, name: u32, value: []const u8) !void {
const rc = ws2_32.setsockopt(self.fd, @intCast(i32, level), @intCast(i32, name), value.ptr, @intCast(i32, value.len));
if (rc == ws2_32.SOCKET_ERROR) {
return switch (ws2_32.WSAGetLastError()) {
.WSANOTINITIALISED => unreachable,
.WSAENETDOWN => return error.NetworkSubsystemFailed,
.WSAEFAULT => unreachable,
.WSAENOTSOCK => return error.FileDescriptorNotASocket,
.WSAEINVAL => return error.SocketNotBound,
.WSAENOTCONN => return error.SocketNotConnected,
.WSAESHUTDOWN => return error.AlreadyShutdown,
else => |err| windows.unexpectedWSAError(err),
};
}
}
/// Have close() or shutdown() syscalls block until all queued messages in the socket have been successfully
/// sent, or if the timeout specified in seconds has been reached. It returns `error.UnsupportedSocketOption`
/// if the host does not support the option for a socket to linger around up until a timeout specified in
/// seconds.
pub fn setLinger(self: Socket, timeout_seconds: ?u16) !void {
const settings = ws2_32.linger{
.l_onoff = @as(u16, @boolToInt(timeout_seconds != null)),
.l_linger = if (timeout_seconds) |seconds| seconds else 0,
};
return self.setOption(ws2_32.SOL_SOCKET, ws2_32.SO_LINGER, mem.asBytes(&settings));
}
/// On connection-oriented sockets, have keep-alive messages be sent periodically. The timing in which keep-alive
/// messages are sent are dependant on operating system settings. It returns `error.UnsupportedSocketOption` if
/// the host does not support periodically sending keep-alive messages on connection-oriented sockets.
pub fn setKeepAlive(self: Socket, enabled: bool) !void {
return self.setOption(ws2_32.SOL_SOCKET, ws2_32.SO_KEEPALIVE, mem.asBytes(&@as(u32, @boolToInt(enabled))));
}
/// Allow multiple sockets on the same host to listen on the same address. It returns `error.UnsupportedSocketOption` if
/// the host does not support sockets listening the same address.
pub fn setReuseAddress(self: Socket, enabled: bool) !void {
return self.setOption(ws2_32.SOL_SOCKET, ws2_32.SO_REUSEADDR, mem.asBytes(&@as(u32, @boolToInt(enabled))));
}
/// Allow multiple sockets on the same host to listen on the same port. It returns `error.UnsupportedSocketOption` if
/// the host does not supports sockets listening on the same port.
///
/// TODO: verify if this truly mimicks SO_REUSEPORT behavior, or if SO_REUSE_UNICASTPORT provides the correct behavior
pub fn setReusePort(self: Socket, enabled: bool) !void {
try self.setOption(ws2_32.SOL_SOCKET, ws2_32.SO_BROADCAST, mem.asBytes(&@as(u32, @boolToInt(enabled))));
try self.setReuseAddress(enabled);
}
/// Set the write buffer size of the socket.
pub fn setWriteBufferSize(self: Socket, size: u32) !void {
return self.setOption(ws2_32.SOL_SOCKET, ws2_32.SO_SNDBUF, mem.asBytes(&size));
}
/// Set the read buffer size of the socket.
pub fn setReadBufferSize(self: Socket, size: u32) !void {
return self.setOption(ws2_32.SOL_SOCKET, ws2_32.SO_RCVBUF, mem.asBytes(&size));
}
/// Set a timeout on the socket that is to occur if no messages are successfully written
/// to its bound destination after a specified number of milliseconds. A subsequent write
/// to the socket will thereafter return `error.WouldBlock` should the timeout be exceeded.
pub fn setWriteTimeout(self: Socket, milliseconds: u32) !void {
return self.setOption(ws2_32.SOL_SOCKET, ws2_32.SO_SNDTIMEO, mem.asBytes(&milliseconds));
}
/// Set a timeout on the socket that is to occur if no messages are successfully read
/// from its bound destination after a specified number of milliseconds. A subsequent
/// read from the socket will thereafter return `error.WouldBlock` should the timeout be
/// exceeded.
pub fn setReadTimeout(self: Socket, milliseconds: u32) !void {
return self.setOption(ws2_32.SOL_SOCKET, ws2_32.SO_RCVTIMEO, mem.asBytes(&milliseconds));
}
};