From 6799cca2a6b2388df48a7f6670379d3da6a96084 Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Sat, 20 Apr 2024 02:30:52 -0400 Subject: [PATCH] tcp: read server preferences from the config --- src/config/Config.zig | 11 +++++++ src/tcp.zig | 1 - src/tcp/Command.zig | 1 - src/tcp/Server.zig | 74 ++++++++++++++++++++++++++++++++----------- src/tcp/Thread.zig | 17 +++++++--- 5 files changed, 80 insertions(+), 24 deletions(-) diff --git a/src/config/Config.zig b/src/config/Config.zig index ace9c5e4a..a7f8c560f 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -1353,6 +1353,17 @@ keybind: Keybinds = .{}, /// If `true`, the bold text will use the bright color palette. @"bold-is-bright": bool = false, +/// If set, Ghostty will start a TCP server on the specified socket path. Valid +/// values can include a valid Unix socket path or an IP address and port. For +/// example, `0.0.0.0:9393` will start a TCP server on all interfaces on port +/// 9393. A value such as `/tmp/ghostty.sock` will start a socket on that path. +@"remote-tcp-socket": ?[:0]const u8 = null, + +/// The maximum number of connections (default 10) that can be made to the +/// remote TCP server. If this limit is reached, new connections will be +/// rejected with a message and the connection will be closed. +@"remote-max-connections": u8 = 10, + /// This will be used to set the `TERM` environment variable. /// HACK: We set this with an `xterm` prefix because vim uses that to enable key /// protocols (specifically this will enable `modifyOtherKeys`), among other diff --git a/src/tcp.zig b/src/tcp.zig index 68450920b..d458eae68 100644 --- a/src/tcp.zig +++ b/src/tcp.zig @@ -2,7 +2,6 @@ //! responding to TCP requests and dispatching them to the app's Mailbox. pub const Thread = @import("tcp/Thread.zig"); pub const Server = @import("tcp/Server.zig"); -pub const Command = @import("tcp/Command.zig").Command; test { @import("std").testing.refAllDecls(@This()); diff --git a/src/tcp/Command.zig b/src/tcp/Command.zig index 11f341f36..8b72fb5d7 100644 --- a/src/tcp/Command.zig +++ b/src/tcp/Command.zig @@ -24,7 +24,6 @@ pub const Command = enum { // Not using .first() because it returns everything if there is no space // Instead we're doing what's equivalent to popping the first element const cmdName = iter.next() orelse return error.InvalidInput; - log.debug("got command name={s}", .{cmdName}); // TODO: Handle/support arguments return std.meta.stringToEnum(Command, cmdName) orelse return error.InvalidCommand; diff --git a/src/tcp/Server.zig b/src/tcp/Server.zig index 406e0eecd..5bc7e8067 100644 --- a/src/tcp/Server.zig +++ b/src/tcp/Server.zig @@ -1,6 +1,6 @@ const std = @import("std"); const xev = @import("xev"); -const tcp = @import("../tcp.zig"); +const Config = @import("../Config.zig").Config; const reject_client = @import("./handlers/reject.zig").reject_client; const read_client = @import("./handlers/reader.zig").read_client; @@ -11,9 +11,6 @@ const SocketPool = std.heap.MemoryPool(xev.TCP); const BufferPool = std.heap.MemoryPool([1024]u8); const log = std.log.scoped(.tcp_thread); -/// Maximum connections we allow at once -const MAX_CLIENTS = 2; - /// Acceptor polling rate in milliseconds const ACCEPTOR_RATE = 1; @@ -32,9 +29,6 @@ sock_pool: SocketPool, /// Memory pool that stores buffers for reading and writing buf_pool: BufferPool, -/// Stop flag -stop: bool, - /// Event loop loop: xev.Loop, @@ -45,32 +39,75 @@ socket: xev.TCP, acceptor: xev.Timer, /// Array of client sockets -clients: [MAX_CLIENTS]*xev.TCP, +/// TODO: Merge with `sock_pool`? or make a hashmap +clients: SocketPool, /// Number of clients connected clients_count: usize, -/// Initializes the server with the given allocator and address -pub fn init(alloc: Allocator, addr: std.net.Address) !Server { - const socket = try xev.TCP.init(addr); - try socket.bind(addr); +/// Address to bind to +addr: std.net.Address, +/// Maximum clients allowed +max_clients: u8, + +/// Initializes the server with the given allocator and address +pub fn init( + alloc: Allocator, + addr: std.net.Address, + max_clients: u8, +) !Server { return Server{ .comp_pool = CompletionPool.init(alloc), .sock_pool = SocketPool.init(alloc), .buf_pool = BufferPool.init(alloc), - .stop = false, .loop = try xev.Loop.init(.{}), - .socket = socket, + .socket = try xev.TCP.init(addr), .acceptor = try xev.Timer.init(), .clients = undefined, .clients_count = 0, .alloc = alloc, + .max_clients = max_clients, + .addr = addr, }; } +const BindError = error{ + NoAddress, + InvalidAddress, +}; + +/// Tries to generate a valid address to bind to +/// TODO: Maybe unix sockets should start with unix:// +pub fn parseAddress(raw_addr: ?[:0]const u8) BindError!std.net.Address { + const addr = raw_addr orelse { + return BindError.NoAddress; + }; + + if (addr.len == 0) { + return BindError.NoAddress; + } + + var iter = std.mem.splitScalar(u8, addr, ':'); + const host = iter.next() orelse return BindError.InvalidAddress; + const port = iter.next() orelse return BindError.InvalidAddress; + const numPort = std.fmt.parseInt(u16, port, 10) catch { + return std.net.Address.initUnix(addr) catch BindError.InvalidAddress; + }; + + const ip = std.net.Address.parseIp4(host, numPort) catch { + return std.net.Address.initUnix(addr) catch BindError.InvalidAddress; + }; + + return ip; +} + /// Deinitializes the server pub fn deinit(self: *Server) void { + log.info("shutting down server", .{}); + var c: xev.Completion = undefined; + self.socket.close(&self.loop, &c, Server, self, closeHandler); + self.buf_pool.deinit(); self.comp_pool.deinit(); self.sock_pool.deinit(); @@ -80,7 +117,8 @@ pub fn deinit(self: *Server) void { /// Starts the timer which tries to accept connections pub fn start(self: *Server) !void { - try self.socket.listen(MAX_CLIENTS); + try self.socket.bind(self.addr); + try self.socket.listen(self.max_clients); log.info("bound server to socket={any}", .{self.socket}); // Each acceptor borrows a completion from the pool @@ -91,7 +129,7 @@ pub fn start(self: *Server) !void { }; self.acceptor.run(&self.loop, c, ACCEPTOR_RATE, Server, self, acceptor); - while (!self.stop) { + while (true) { try self.loop.run(.until_done); } } @@ -153,14 +191,14 @@ fn acceptHandler( return .disarm; }; - if (self.clients_count == MAX_CLIENTS) { + if (self.clients_count == self.max_clients) { log.warn("max clients reached, rejecting fd={d}", .{sock.fd}); reject_client(self, sock) catch return .rearm; return .disarm; } log.info("accepted connection fd={d}", .{sock.fd}); - self.clients[self.clients_count] = sock; + // self.clients[self.clients_count] = sock; self.clients_count += 1; read_client(self, sock, c) catch { diff --git a/src/tcp/Thread.zig b/src/tcp/Thread.zig index 3e0f86a9a..309e9c1d0 100644 --- a/src/tcp/Thread.zig +++ b/src/tcp/Thread.zig @@ -5,6 +5,7 @@ const std = @import("std"); const xev = @import("xev"); const tcp = @import("../tcp.zig"); const App = @import("../App.zig"); +const Config = @import("../Config.zig").Config; const Allocator = std.mem.Allocator; const log = std.log.scoped(.tcp_thread); @@ -19,9 +20,17 @@ server: tcp.Server, /// up all the internal state necessary prior to starting the thread. It /// is up to the caller to start the thread with the threadMain entrypoint. pub fn init(alloc: Allocator) !Thread { - // TODO: Configurable addresses and socket paths - const addr = try std.net.Address.parseIp4("127.0.0.1", 9090); - var server = try tcp.Server.init(alloc, addr); + const config = try Config.load(alloc); + const max_clients = config.@"remote-max-connections"; + const addr = config.@"remote-tcp-socket"; + + const parsedAddr = tcp.Server.parseAddress(addr) catch |err| { + log.err("failed to parse address addr={any} err={any}", .{ addr, err }); + return err; + }; + + log.debug("parsed address addr={any}", .{parsedAddr}); + var server = try tcp.Server.init(alloc, parsedAddr, max_clients); errdefer server.deinit(); return Thread{ @@ -45,6 +54,6 @@ pub fn threadMain(self: *Thread) void { fn threadMain_(self: *Thread) !void { log.debug("starting tcp thread", .{}); - defer log.debug("tcp thread exited", .{}); try self.server.start(); + errdefer log.debug("tcp thread exited", .{}); }