authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-04-25 19:15:15-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2021-04-25 19:15:15-04:00
logc420eb60ad17599f035594ba877df66b7705fb25
treea1683f03f85fc9728ebf41f8132f3b7b3505bfeb
parent2fc6b347ec66650cd1702c63104fc45148658b15
parent98706c968677caafeb12c56c884b7cc952c696bc
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #8590 from lithdew/master

x, x/os/Socket: initial work on new Socket abstraction

5 files changed, 288 insertions(+), 1 deletions(-)

lib/std/os/bits/openbsd.zig+1-1
...@@ -805,7 +805,7 @@ comptime {...@@ -805,7 +805,7 @@ comptime {
805 if (@sizeOf(usize) == 4)805 if (@sizeOf(usize) == 4)
806 std.debug.assert(@sizeOf(siginfo_t) == 128)806 std.debug.assert(@sizeOf(siginfo_t) == 128)
807 else807 else
808 // Take into account the padding between errno and data fields.808 // Take into account the padding between errno and data fields.
809 std.debug.assert(@sizeOf(siginfo_t) == 136);809 std.debug.assert(@sizeOf(siginfo_t) == 136);
810}810}
811811
lib/std/std.zig+1
...@@ -88,6 +88,7 @@ pub const time = @import("time.zig");...@@ -88,6 +88,7 @@ pub const time = @import("time.zig");
88pub const unicode = @import("unicode.zig");88pub const unicode = @import("unicode.zig");
89pub const valgrind = @import("valgrind.zig");89pub const valgrind = @import("valgrind.zig");
90pub const wasm = @import("wasm.zig");90pub const wasm = @import("wasm.zig");
91pub const x = @import("x.zig");
91pub const zig = @import("zig.zig");92pub const zig = @import("zig.zig");
92pub const start = @import("start.zig");93pub const start = @import("start.zig");
9394
lib/std/x.zig created+1
...@@ -0,0 +1 @@
1pub const os = @import("x/os/os.zig");
lib/std/x/os/Socket.zig created+276
...@@ -0,0 +1,276 @@
1const std = @import("../../std.zig");
2
3const os = std.os;
4const mem = std.mem;
5const net = std.net;
6const time = std.time;
7const builtin = std.builtin;
8const testing = std.testing;
9
10const Socket = @This();
11
12/// A socket-address pair.
13pub const Connection = struct {
14 socket: Socket,
15 address: net.Address,
16};
17
18/// The underlying handle of a socket.
19fd: os.socket_t,
20
21/// Open a new socket.
22pub fn init(domain: u32, socket_type: u32, protocol: u32) !Socket {
23 return Socket{ .fd = try os.socket(domain, socket_type, protocol) };
24}
25
26/// Closes the socket.
27pub fn deinit(self: Socket) void {
28 os.closeSocket(self.fd);
29}
30
31/// Shutdown either the read side, or write side, or the entirety of a socket.
32pub fn shutdown(self: Socket, how: os.ShutdownHow) !void {
33 return os.shutdown(self.fd, how);
34}
35
36/// Binds the socket to an address.
37pub fn bind(self: Socket, address: net.Address) !void {
38 return os.bind(self.fd, &address.any, address.getOsSockLen());
39}
40
41/// Start listening for incoming connections on the socket.
42pub fn listen(self: Socket, max_backlog_size: u31) !void {
43 return os.listen(self.fd, max_backlog_size);
44}
45
46/// Have the socket attempt to the connect to an address.
47pub fn connect(self: Socket, address: net.Address) !void {
48 return os.connect(self.fd, &address.any, address.getOsSockLen());
49}
50
51/// Accept a pending incoming connection queued to the kernel backlog
52/// of the socket.
53pub fn accept(self: Socket, flags: u32) !Socket.Connection {
54 var address: os.sockaddr = undefined;
55 var address_len: u32 = @sizeOf(os.sockaddr);
56
57 const fd = try os.accept(self.fd, &address, &address_len, flags);
58
59 return Connection{
60 .socket = Socket{ .fd = fd },
61 .address = net.Address.initPosix(@alignCast(4, &address)),
62 };
63}
64
65/// Read data from the socket into the buffer provided. It returns the
66/// number of bytes read into the buffer provided.
67pub fn read(self: Socket, buf: []u8) !usize {
68 return os.read(self.fd, buf);
69}
70
71/// Read data from the socket into the buffer provided with a set of flags
72/// specified. It returns the number of bytes read into the buffer provided.
73pub fn recv(self: Socket, buf: []u8, flags: u32) !usize {
74 return os.recv(self.fd, buf, flags);
75}
76
77/// Write a buffer of data provided to the socket. It returns the number
78/// of bytes that are written to the socket.
79pub fn write(self: Socket, buf: []const u8) !usize {
80 return os.write(self.fd, buf);
81}
82
83/// Writes multiple I/O vectors to the socket. It returns the number
84/// of bytes that are written to the socket.
85pub fn writev(self: Socket, buffers: []const os.iovec_const) !usize {
86 return os.writev(self.fd, buffers);
87}
88
89/// Write a buffer of data provided to the socket with a set of flags specified.
90/// It returns the number of bytes that are written to the socket.
91pub fn send(self: Socket, buf: []const u8, flags: u32) !usize {
92 return os.send(self.fd, buf, flags);
93}
94
95/// Writes multiple I/O vectors with a prepended message header to the socket
96/// with a set of flags specified. It returns the number of bytes that are
97/// written to the socket.
98pub fn sendmsg(self: Socket, msg: os.msghdr_const, flags: u32) !usize {
99 return os.sendmsg(self.fd, msg, flags);
100}
101
102/// Query the address that the socket is locally bounded to.
103pub fn getLocalAddress(self: Socket) !net.Address {
104 var address: os.sockaddr = undefined;
105 var address_len: u32 = @sizeOf(os.sockaddr);
106 try os.getsockname(self.fd, &address, &address_len);
107 return net.Address.initPosix(@alignCast(4, &address));
108}
109
110/// Query and return the latest cached error on the socket.
111pub fn getError(self: Socket) !void {
112 return os.getsockoptError(self.fd);
113}
114
115/// Query the read buffer size of the socket.
116pub fn getReadBufferSize(self: Socket) !u32 {
117 var value: u32 = undefined;
118 var value_len: u32 = @sizeOf(u32);
119
120 const rc = os.system.getsockopt(self.fd, os.SOL_SOCKET, os.SO_RCVBUF, mem.asBytes(&value), &value_len);
121 return switch (os.errno(rc)) {
122 0 => value,
123 os.EBADF => error.BadFileDescriptor,
124 os.EFAULT => error.InvalidAddressSpace,
125 os.EINVAL => error.InvalidSocketOption,
126 os.ENOPROTOOPT => error.UnknownSocketOption,
127 os.ENOTSOCK => error.NotASocket,
128 else => |err| os.unexpectedErrno(err),
129 };
130}
131
132/// Query the write buffer size of the socket.
133pub fn getWriteBufferSize(self: Socket) !u32 {
134 var value: u32 = undefined;
135 var value_len: u32 = @sizeOf(u32);
136
137 const rc = os.system.getsockopt(self.fd, os.SOL_SOCKET, os.SO_SNDBUF, mem.asBytes(&value), &value_len);
138 return switch (os.errno(rc)) {
139 0 => value,
140 os.EBADF => error.BadFileDescriptor,
141 os.EFAULT => error.InvalidAddressSpace,
142 os.EINVAL => error.InvalidSocketOption,
143 os.ENOPROTOOPT => error.UnknownSocketOption,
144 os.ENOTSOCK => error.NotASocket,
145 else => |err| os.unexpectedErrno(err),
146 };
147}
148
149/// Allow multiple sockets on the same host to listen on the same address. It returns `error.UnsupportedSocketOption` if
150/// the host does not support sockets listening the same address.
151pub fn setReuseAddress(self: Socket, enabled: bool) !void {
152 if (comptime @hasDecl(os, "SO_REUSEADDR")) {
153 return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_REUSEADDR, mem.asBytes(&@as(usize, @boolToInt(enabled))));
154 }
155 return error.UnsupportedSocketOption;
156}
157
158/// Allow multiple sockets on the same host to listen on the same port. It returns `error.UnsupportedSocketOption` if
159/// the host does not supports sockets listening on the same port.
160pub fn setReusePort(self: Socket, enabled: bool) !void {
161 if (comptime @hasDecl(os, "SO_REUSEPORT")) {
162 return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_REUSEPORT, mem.asBytes(&@as(usize, @boolToInt(enabled))));
163 }
164 return error.UnsupportedSocketOption;
165}
166
167/// Disable Nagle's algorithm on a TCP socket. It returns `error.UnsupportedSocketOption` if the host does not support
168/// sockets disabling Nagle's algorithm.
169pub fn setNoDelay(self: Socket, enabled: bool) !void {
170 if (comptime @hasDecl(os, "TCP_NODELAY")) {
171 return os.setsockopt(self.fd, os.IPPROTO_TCP, os.TCP_NODELAY, mem.asBytes(&@as(usize, @boolToInt(enabled))));
172 }
173 return error.UnsupportedSocketOption;
174}
175
176/// Enables TCP Fast Open (RFC 7413) on a TCP socket. It returns `error.UnsupportedSocketOption` if the host does not
177/// support TCP Fast Open.
178pub fn setFastOpen(self: Socket, enabled: bool) !void {
179 if (comptime @hasDecl(os, "TCP_FASTOPEN")) {
180 return os.setsockopt(self.fd, os.IPPROTO_TCP, os.TCP_FASTOPEN, mem.asBytes(&@as(usize, @boolToInt(enabled))));
181 }
182 return error.UnsupportedSocketOption;
183}
184
185/// Enables TCP Quick ACK on a TCP socket to immediately send rather than delay ACKs when necessary. It returns
186/// `error.UnsupportedSocketOption` if the host does not support TCP Quick ACK.
187pub fn setQuickACK(self: Socket, enabled: bool) !void {
188 if (comptime @hasDecl(os, "TCP_QUICKACK")) {
189 return os.setsockopt(self.fd, os.IPPROTO_TCP, os.TCP_QUICKACK, mem.asBytes(&@as(usize, @boolToInt(enabled))));
190 }
191 return error.UnsupportedSocketOption;
192}
193
194/// Set the write buffer size of the socket.
195pub fn setWriteBufferSize(self: Socket, size: u32) !void {
196 return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_SNDBUF, mem.asBytes(&size));
197}
198
199/// Set the read buffer size of the socket.
200pub fn setReadBufferSize(self: Socket, size: u32) !void {
201 return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_RCVBUF, mem.asBytes(&size));
202}
203
204/// Set a timeout on the socket that is to occur if no messages are successfully written
205/// to its bound destination after a specified number of milliseconds. A subsequent write
206/// to the socket will thereafter return `error.WouldBlock` should the timeout be exceeded.
207pub fn setWriteTimeout(self: Socket, milliseconds: usize) !void {
208 const timeout = os.timeval{
209 .tv_sec = @intCast(isize, milliseconds / time.ms_per_s),
210 .tv_usec = @intCast(isize, (milliseconds % time.ms_per_s) * time.us_per_ms),
211 };
212
213 return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_SNDTIMEO, mem.asBytes(&timeout));
214}
215
216/// Set a timeout on the socket that is to occur if no messages are successfully read
217/// from its bound destination after a specified number of milliseconds. A subsequent
218/// read from the socket will thereafter return `error.WouldBlock` should the timeout be
219/// exceeded.
220pub fn setReadTimeout(self: Socket, milliseconds: usize) !void {
221 const timeout = os.timeval{
222 .tv_sec = @intCast(isize, milliseconds / time.ms_per_s),
223 .tv_usec = @intCast(isize, (milliseconds % time.ms_per_s) * time.us_per_ms),
224 };
225
226 return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_RCVTIMEO, mem.asBytes(&timeout));
227}
228
229test {
230 testing.refAllDecls(@This());
231}
232
233test "socket/linux: set read timeout of 1 millisecond on blocking socket" {
234 if (builtin.os.tag != .linux) return error.SkipZigTest;
235
236 const a = try Socket.init(os.AF_INET, os.SOCK_STREAM | os.SOCK_CLOEXEC, os.IPPROTO_TCP);
237 defer a.deinit();
238
239 try a.bind(net.Address.initIp4([_]u8{ 0, 0, 0, 0 }, 0));
240 try a.listen(128);
241
242 const binded_address = try a.getLocalAddress();
243
244 const b = try Socket.init(os.AF_INET, os.SOCK_STREAM | os.SOCK_CLOEXEC, os.IPPROTO_TCP);
245 defer b.deinit();
246
247 try b.connect(binded_address);
248 try b.setReadTimeout(1);
249
250 const ab = try a.accept(os.SOCK_CLOEXEC);
251 defer ab.socket.deinit();
252
253 var buf: [1]u8 = undefined;
254 testing.expectError(error.WouldBlock, b.read(&buf));
255}
256
257test "socket/linux: create non-blocking socket pair" {
258 if (builtin.os.tag != .linux) return error.SkipZigTest;
259
260 const a = try Socket.init(os.AF_INET, os.SOCK_STREAM | os.SOCK_NONBLOCK | os.SOCK_CLOEXEC, os.IPPROTO_TCP);
261 defer a.deinit();
262
263 try a.bind(net.Address.initIp4([_]u8{ 0, 0, 0, 0 }, 0));
264 try a.listen(128);
265
266 const binded_address = try a.getLocalAddress();
267
268 const b = try Socket.init(os.AF_INET, os.SOCK_STREAM | os.SOCK_NONBLOCK | os.SOCK_CLOEXEC, os.IPPROTO_TCP);
269 defer b.deinit();
270
271 testing.expectError(error.WouldBlock, b.connect(binded_address));
272 try b.getError();
273
274 const ab = try a.accept(os.SOCK_NONBLOCK | os.SOCK_CLOEXEC);
275 defer ab.socket.deinit();
276}
lib/std/x/os/os.zig created+9
...@@ -0,0 +1,9 @@
1const std = @import("../../std.zig");
2
3const testing = std.testing;
4
5pub const Socket = @import("Socket.zig");
6
7test {
8 testing.refAllDecls(@This());
9}