1const std = @import("std");
2const builtin = @import("builtin");
3const nio = @import("nio");
4const nfs = @import("nfs");
5const time = @import("time");
6const url = @import("url");
7const sys_linux = @import("sys-linux");
8
9const os = builtin.target.os.tag;
10
11const 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
19pub const off_t = sys.off_t;
20
21pub 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
133pub 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
154pub 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
177pub 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
185pub 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
299pub 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
328pub const getaddrinfo = sys.getaddrinfo;
329
330pub const freeaddrinfo = sys.freeaddrinfo;