| ... | ... | @@ -0,0 +1,276 @@ |
| 1 | const std = @import("../../std.zig"); |
| 2 | |
| 3 | const os = std.os; |
| 4 | const mem = std.mem; |
| 5 | const net = std.net; |
| 6 | const time = std.time; |
| 7 | const builtin = std.builtin; |
| 8 | const testing = std.testing; |
| 9 | |
| 10 | const Socket = @This(); |
| 11 | |
| 12 | /// A socket-address pair. |
| 13 | pub const Connection = struct { |
| 14 | socket: Socket, |
| 15 | address: net.Address, |
| 16 | }; |
| 17 | |
| 18 | /// The underlying handle of a socket. |
| 19 | fd: os.socket_t, |
| 20 | |
| 21 | /// Open a new socket. |
| 22 | pub 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. |
| 27 | pub 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. |
| 32 | pub fn shutdown(self: Socket, how: os.ShutdownHow) !void { |
| 33 | return os.shutdown(self.fd, how); |
| 34 | } |
| 35 | |
| 36 | /// Binds the socket to an address. |
| 37 | pub 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. |
| 42 | pub 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. |
| 47 | pub 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. |
| 53 | pub 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. |
| 67 | pub 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. |
| 73 | pub 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. |
| 79 | pub 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. |
| 85 | pub 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. |
| 91 | pub 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. |
| 98 | pub 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. |
| 103 | pub 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. |
| 111 | pub fn getError(self: Socket) !void { |
| 112 | return os.getsockoptError(self.fd); |
| 113 | } |
| 114 | |
| 115 | /// Query the read buffer size of the socket. |
| 116 | pub 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. |
| 133 | pub 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. |
| 151 | pub 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. |
| 160 | pub 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. |
| 169 | pub 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. |
| 178 | pub 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. |
| 187 | pub 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. |
| 195 | pub 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. |
| 200 | pub 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. |
| 207 | pub 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. |
| 220 | pub 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 | |
| 229 | test { |
| 230 | testing.refAllDecls(@This()); |
| 231 | } |
| 232 | |
| 233 | test "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 | |
| 257 | test "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 | } |
| | \ No newline at end of file |