| 1 | const std = @import("std"); |
| 2 | const builtin = @import("builtin"); |
| 3 | const nio = @import("nio"); |
| 4 | const nfs = @import("nfs"); |
| 5 | const time = @import("time"); |
| 6 | const url = @import("url"); |
| 7 | const sys_linux = @import("sys-linux"); |
| 8 | |
| 9 | const os = builtin.target.os.tag; |
| 10 | |
| 11 | const sys = switch (os) { |
| 12 | .linux => sys_linux, |
| 13 | .freebsd => @import("sys-freebsd"), |
| 14 | .netbsd => @import("sys-netbsd"), |
| 15 | .openbsd => @import("sys-openbsd"), |
| 16 | else => @compileError("TODO"), |
| 17 | }; |
| 18 | |
| 19 | pub const off_t = sys.off_t; |
| 20 | |
| 21 | pub const Address = extern union { |
| 22 | any: sys.struct_sockaddr, |
| 23 | in: Ip4Address, |
| 24 | in6: Ip6Address, |
| 25 | |
| 26 | pub fn initIp4(addr: [4]u8, hport: u16) Address { |
| 27 | return .{ .in = Ip4Address.init(addr, hport) }; |
| 28 | } |
| 29 | |
| 30 | pub fn initIp6(addr: [8]u16, hport: u16) Address { |
| 31 | return .{ .in6 = Ip6Address.init(addr, hport) }; |
| 32 | } |
| 33 | |
| 34 | pub fn fromUrl(u: *const url.URL, allocator: std.mem.Allocator) !Address { |
| 35 | return switch (u.hostFancy()) { |
| 36 | .unset => unreachable, |
| 37 | .ipv4 => |int| .initIp4(@bitCast(int), u.portFancy().?), |
| 38 | .ipv6 => |int| .initIp6(@bitCast(int), u.portFancy().?), |
| 39 | .name => |hostname| blk: { |
| 40 | const hostnamez = try allocator.dupeZ(u8, hostname); |
| 41 | defer allocator.free(hostnamez); |
| 42 | const portz = try nio.fmt.allocPrintZ(allocator, "{d}", .{u.portFancy().?}); |
| 43 | defer allocator.free(portz); |
| 44 | const gai = try getaddrinfo(hostnamez, portz, null); |
| 45 | defer freeaddrinfo(gai); |
| 46 | break :blk switch (gai.addr.?.family) { |
| 47 | .INET => .{ .in = .{ .sa = @as(*Ip4Address.SockAddr, @ptrCast(@alignCast(gai.addr.?))).* } }, |
| 48 | .INET6 => .{ .in6 = .{ .sa = @as(*Ip6Address.SockAddr, @ptrCast(@alignCast(gai.addr.?))).* } }, |
| 49 | else => @panic("TODO"), |
| 50 | }; |
| 51 | }, |
| 52 | }; |
| 53 | } |
| 54 | |
| 55 | pub fn nprint(adr: Address, writer: anytype) !void { |
| 56 | switch (adr.any.family) { |
| 57 | .INET => try adr.in.nprint(writer), |
| 58 | .INET6 => try adr.in6.nprint(writer), |
| 59 | else => |a| try writer.print("{{any:{s}}}", .{@tagName(a)}), |
| 60 | } |
| 61 | } |
| 62 | |
| 63 | pub fn size(adr: Address) sys.socklen_t { |
| 64 | return switch (adr.any.family) { |
| 65 | .INET => @sizeOf(sys.struct_sockaddr_in), |
| 66 | .INET6 => @sizeOf(sys.struct_sockaddr_in6), |
| 67 | .UNIX => @sizeOf(sys.struct_sockaddr_un), |
| 68 | else => unreachable, |
| 69 | }; |
| 70 | } |
| 71 | |
| 72 | pub fn port(adr: Address) u16 { |
| 73 | return std.mem.bigToNative(u16, switch (adr.any.family) { |
| 74 | .INET => adr.in.sa.port, |
| 75 | .INET6 => adr.in6.sa.port, |
| 76 | else => unreachable, |
| 77 | }); |
| 78 | } |
| 79 | |
| 80 | pub fn listen(adr_: Address, options: ListenOptions) !Server { |
| 81 | var adr = adr_; |
| 82 | if (options.dualstack_if_possible and adr_.any.family == .INET and adr_.in.sa.addr.addr == 0) { |
| 83 | adr = .{ .in6 = .init(@bitCast(@as([16]u8, @splat(0))), @byteSwap(adr_.in.sa.port)) }; |
| 84 | } |
| 85 | const sockfd = try sys.socket( |
| 86 | adr.any.family, |
| 87 | sys.SOCK.STREAM | sys.SOCK.CLOEXEC, |
| 88 | if (adr.any.family == sys.AF.UNIX) 0 else sys.IPPROTO.TCP, |
| 89 | ); |
| 90 | var s: Server = .{ |
| 91 | .address = undefined, |
| 92 | .stream = .{ .socket = @enumFromInt(sockfd) }, |
| 93 | }; |
| 94 | errdefer s.stream.close(); |
| 95 | |
| 96 | if (options.dualstack_if_possible and adr_.any.family == .INET and adr_.in.sa.addr.addr == 0) { |
| 97 | try sys.setsockopt(sockfd, sys.IPPROTO.IPV6, sys.IPV6.V6ONLY, &std.mem.toBytes(@as(c_int, 0))); |
| 98 | } |
| 99 | if (options.reuse_address) { |
| 100 | try sys.setsockopt(sockfd, sys.SOL.SOCKET, sys.SO.REUSEADDR, &std.mem.toBytes(@as(c_int, 1))); |
| 101 | |
| 102 | if (adr.any.family != sys.AF.UNIX) { |
| 103 | try sys.setsockopt(sockfd, sys.SOL.SOCKET, sys.SO.REUSEPORT, &std.mem.toBytes(@as(c_int, 1))); |
| 104 | } |
| 105 | } |
| 106 | |
| 107 | var socklen = adr.size(); |
| 108 | try sys.bind(sockfd, &adr.any, socklen); |
| 109 | try sys.listen(sockfd, options.kernel_backlog); |
| 110 | try sys.getsockname(sockfd, &s.address.any, &socklen); |
| 111 | return s; |
| 112 | } |
| 113 | |
| 114 | pub const ListenOptions = struct { |
| 115 | /// How many connections the kernel will accept on the application's behalf. |
| 116 | /// If more than this many connections pool in the kernel, clients will start seeing "Connection refused". |
| 117 | kernel_backlog: u31 = 511, |
| 118 | /// Sets SO_REUSEADDR and SO_REUSEPORT. |
| 119 | reuse_address: bool = false, |
| 120 | /// When listening on 0.0.0.0, listens on [::] instead and disables IPV6_V6ONLY. |
| 121 | dualstack_if_possible: bool = true, |
| 122 | }; |
| 123 | |
| 124 | pub fn tcpConnect(adr: Address) !Stream { |
| 125 | const fd = try sys.socket(adr.any.family, sys.SOCK.STREAM | sys.SOCK.CLOEXEC, sys.IPPROTO.TCP); |
| 126 | const stream: Stream = .{ .socket = @enumFromInt(fd) }; |
| 127 | errdefer stream.close(); |
| 128 | try sys.connect(fd, &adr.any, adr.size()); |
| 129 | return stream; |
| 130 | } |
| 131 | }; |
| 132 | |
| 133 | pub const Ip4Address = extern struct { |
| 134 | sa: sys.struct_sockaddr_in, |
| 135 | |
| 136 | pub const SockAddr = sys.struct_sockaddr_in; |
| 137 | |
| 138 | pub fn init(addr: [4]u8, port: u16) Ip4Address { |
| 139 | return Ip4Address{ |
| 140 | .sa = .{ |
| 141 | .port = std.mem.nativeToBig(u16, port), |
| 142 | .addr = .{ .addr = @bitCast(addr) }, |
| 143 | }, |
| 144 | }; |
| 145 | } |
| 146 | |
| 147 | pub fn nprint(adr: Ip4Address, writer: anytype) !void { |
| 148 | const parts: [4]u8 = @bitCast(adr.sa.addr.addr); |
| 149 | const port = @byteSwap(adr.sa.port); |
| 150 | try writer.print("{d}.{d}.{d}.{d}:{d}", .{ parts[0], parts[1], parts[2], parts[3], port }); |
| 151 | } |
| 152 | }; |
| 153 | |
| 154 | pub const Ip6Address = extern struct { |
| 155 | sa: sys.struct_sockaddr_in6, |
| 156 | |
| 157 | pub const SockAddr = sys.struct_sockaddr_in6; |
| 158 | |
| 159 | pub fn init(addr: [8]u16, port: u16) Ip6Address { |
| 160 | return Ip6Address{ |
| 161 | .sa = .{ |
| 162 | .port = std.mem.nativeToBig(u16, port), |
| 163 | .addr = .{ .addr = @bitCast(addr) }, |
| 164 | .flowinfo = 0, |
| 165 | .scope_id = 0, |
| 166 | }, |
| 167 | }; |
| 168 | } |
| 169 | |
| 170 | pub fn nprint(adr: Ip6Address, writer: anytype) !void { |
| 171 | const parts: [8]u16 = @bitCast(adr.sa.addr.addr); |
| 172 | const port = @byteSwap(adr.sa.port); |
| 173 | try writer.print("[{x:0>4}:{x:0>4}:{x:0>4}:{x:0>4}:{x:0>4}:{x:0>4}:{x:0>4}:{x:0>4}]:{d}", .{ parts[0], parts[1], parts[2], parts[3], parts[4], parts[5], parts[6], parts[7], port }); |
| 174 | } |
| 175 | }; |
| 176 | |
| 177 | pub const Socket = switch (os) { |
| 178 | .linux => enum(c_uint) { _ }, |
| 179 | .freebsd => enum(c_uint) { _ }, |
| 180 | .netbsd => enum(c_uint) { _ }, |
| 181 | .openbsd => enum(c_uint) { _ }, |
| 182 | else => @compileError("TODO"), |
| 183 | }; |
| 184 | |
| 185 | pub const Stream = struct { |
| 186 | socket: Socket, |
| 187 | |
| 188 | // Resource allocation may fail; resource deallocation must succeed. |
| 189 | pub fn close(s: Stream) void { |
| 190 | sys.close(@intCast(@intFromEnum(s.socket))) catch if (builtin.mode == .Debug) unreachable; |
| 191 | } |
| 192 | |
| 193 | pub fn shutdown(s: Stream, how: sys.SHUT) !void { |
| 194 | return sys.shutdown(@intCast(@intFromEnum(s.socket)), how); |
| 195 | } |
| 196 | |
| 197 | pub fn sendfile(s: Stream, file: nfs.File, offset: off_t, count: ?usize) !void { |
| 198 | switch (os) { |
| 199 | .freebsd, |
| 200 | .netbsd, |
| 201 | .openbsd, |
| 202 | => { |
| 203 | const count_actual = count orelse (try file.stat()).size; |
| 204 | const region = try file.mmapRegion(offset, count_actual); |
| 205 | defer nfs.munmap(region); |
| 206 | try s.writeAll(region); |
| 207 | return; |
| 208 | }, |
| 209 | .linux, |
| 210 | => {}, |
| 211 | else => comptime unreachable, |
| 212 | } |
| 213 | const count_actual = count orelse (try file.stat()).size; |
| 214 | var total: u63 = 0; |
| 215 | while (total < count_actual) { |
| 216 | const len = try sys.sendfile(@intCast(@intFromEnum(s.socket)), @intFromEnum(file.fd), &(offset + total), count_actual - total); |
| 217 | total += @intCast(len); |
| 218 | } |
| 219 | } |
| 220 | |
| 221 | const R = nio.Readable(@This(), ._bare); |
| 222 | pub const readAll = R.readAll; |
| 223 | pub const readAtLeast = R.readAtLeast; |
| 224 | pub const readNoEof = R.readNoEof; |
| 225 | pub const readAllAlloc = R.readAllAlloc; |
| 226 | pub const readArray = R.readArray; |
| 227 | pub const readByte = R.readByte; |
| 228 | pub const readUntilDelimiterArrayList = R.readUntilDelimiterArrayList; |
| 229 | pub const readUntilDelimiterAlloc = R.readUntilDelimiterAlloc; |
| 230 | pub const readUntilDelimiterOrEofAlloc = R.readUntilDelimiterOrEofAlloc; |
| 231 | pub const readUntilDelimitersBuf = R.readUntilDelimitersBuf; |
| 232 | pub const readUntilDelimitersArrayList = R.readUntilDelimitersArrayList; |
| 233 | pub const readAlloc = R.readAlloc; |
| 234 | pub const readInt = R.readInt; |
| 235 | pub const readUntilDelimitersAlloc = R.readUntilDelimitersAlloc; |
| 236 | |
| 237 | pub const ReadError = sys.errno.Error; |
| 238 | pub fn read(s: Stream, buffer: []u8) ReadError!usize { |
| 239 | return sys.recv(@intFromEnum(s.socket), buffer, 0); |
| 240 | } |
| 241 | pub fn anyReadable(s: Stream) nio.AnyReadable { |
| 242 | const S = struct { |
| 243 | fn read(state: *allowzero anyopaque, buffer: []u8) anyerror!usize { |
| 244 | const reified: Stream = .{ .socket = @enumFromInt(@intFromPtr(state)) }; |
| 245 | return reified.read(buffer); |
| 246 | } |
| 247 | }; |
| 248 | return .{ |
| 249 | .vtable = &.{ .read = S.read }, |
| 250 | .state = @ptrFromInt(@intFromEnum(s.socket)), |
| 251 | }; |
| 252 | } |
| 253 | |
| 254 | const W = nio.Writable(@This(), ._bare); |
| 255 | pub const writeAll = W.writeAll; |
| 256 | pub const writevAll = W.writevAll; |
| 257 | pub const writeByteNTimes = W.writeByteNTimes; |
| 258 | pub const writeNTimes = W.writeNTimes; |
| 259 | pub const writeInt = W.writeInt; |
| 260 | pub const writeStruct = W.writeStruct; |
| 261 | pub const writeIntPretty = W.writeIntPretty; |
| 262 | pub const print = W.print; |
| 263 | |
| 264 | pub const WriteError = sys.errno.Error; |
| 265 | pub fn write(s: Stream, bytes: []const u8) WriteError!usize { |
| 266 | return sys.send(@intFromEnum(s.socket), bytes, 0); |
| 267 | } |
| 268 | |
| 269 | pub fn anyWritable(s: Stream) nio.AnyWritable { |
| 270 | const S = struct { |
| 271 | fn write(state: *allowzero anyopaque, buffer: []u8) anyerror!usize { |
| 272 | const reified: Stream = .{ .socket = @enumFromInt(@intFromPtr(state)) }; |
| 273 | return reified.write(buffer); |
| 274 | } |
| 275 | }; |
| 276 | return .{ |
| 277 | .vtable = &.{ .write = S.write }, |
| 278 | .state = @ptrFromInt(@intFromEnum(s.socket)), |
| 279 | }; |
| 280 | } |
| 281 | |
| 282 | pub fn setSendTimeout(s: Stream, timeout_us: u31) !void { |
| 283 | const timeval: sys.struct_timeval = .{ |
| 284 | .sec = timeout_us / time.us_per_s, |
| 285 | .usec = timeout_us % time.us_per_s, |
| 286 | }; |
| 287 | return sys.setsockopt(@intCast(@intFromEnum(s.socket)), sys.SOL.SOCKET, sys.SO.SNDTIMEO, @ptrCast((&timeval)[0..1])); |
| 288 | } |
| 289 | |
| 290 | pub fn setRecvTimeout(s: Stream, timeout_us: u31) !void { |
| 291 | const timeval: sys.struct_timeval = .{ |
| 292 | .sec = timeout_us / time.us_per_s, |
| 293 | .usec = timeout_us % time.us_per_s, |
| 294 | }; |
| 295 | return sys.setsockopt(@intCast(@intFromEnum(s.socket)), sys.SOL.SOCKET, sys.SO.RCVTIMEO, @ptrCast((&timeval)[0..1])); |
| 296 | } |
| 297 | }; |
| 298 | |
| 299 | pub const Server = struct { |
| 300 | address: Address, |
| 301 | stream: Stream, |
| 302 | |
| 303 | pub fn close(s: *const Server) void { |
| 304 | s.stream.close(); |
| 305 | } |
| 306 | |
| 307 | /// Blocks until a client connects to the server. The returned `Connection` has an open stream. |
| 308 | pub fn accept(s: *const Server) !Connection { |
| 309 | var accepted_addr: Address = undefined; |
| 310 | var addr_len: sys.socklen_t = @sizeOf(Address); |
| 311 | const fd = try sys.accept4(@intFromEnum(s.stream.socket), &accepted_addr.any, &addr_len, sys.SOCK.CLOEXEC); |
| 312 | return .{ |
| 313 | .address = accepted_addr, |
| 314 | .stream = .{ .socket = @enumFromInt(fd) }, |
| 315 | }; |
| 316 | } |
| 317 | |
| 318 | pub const Connection = struct { |
| 319 | address: Address, |
| 320 | stream: Stream, |
| 321 | |
| 322 | pub fn close(c: *const Connection) void { |
| 323 | c.stream.close(); |
| 324 | } |
| 325 | }; |
| 326 | }; |
| 327 | |
| 328 | pub const getaddrinfo = sys.getaddrinfo; |
| 329 | |
| 330 | pub const freeaddrinfo = sys.freeaddrinfo; |