Skip to content

Commit

Permalink
feat: tls implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
sectasy0 committed Nov 11, 2024
1 parent 96962c4 commit bcfb93f
Show file tree
Hide file tree
Showing 17 changed files with 529 additions and 32 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ log/*
*.zcpf
persist/*
zcached.conf
.DS_Store
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,19 @@ Crafted using Zig, a versatile, modern, compiled programming language, `zcached`
- **Optimized Efficiency**: Prioritizing swift data handling, `zcached` ensures prompt operations to cater to diverse application needs.
- **Diverse Data Type Support**: Accommodates various data structures like strings, integers, floats, and lists, enhancing utility across different use cases.
- **Evented I/O and Multithreading**: Leveraging evented I/O mechanisms and multithreading capabilities, zcached efficiently manages concurrent operations, enhancing responsiveness and scalability.
- **TLS Support**: Ensures secure data transmission with encryption, protecting data integrity and confidentiality during client-server communication.

## Usage
While `zcached` lacks a CLI, you can utilize nc (netcat) from the terminal to send commands to the server.

#### SET
Set a key to hold the string value. If key already holds a value, it is overwritten, regardless of its type.
```bash
echo "*3\r\n\$3\r\nSET\r\n\$9\r\nmycounter\r\n:42\r\n" | netcat -N localhost 7556
echo "*3\r\n\$3\r\nSET\r\n\$9\r\nmycounter\r\n:42\r\nx03" | netcat -N localhost 7556
```

```bash
echo "*3\r\n\$3\r\nSET\r\n\$9\r\nmycounter\r\n%2\r\n+first\r\n:1\r\n+second\r\n:2\r\n" | netcat -N localhost 7556
echo "*3\r\n\$3\r\nSET\r\n\$9\r\nmycounter\r\n%2\r\n+first\r\n:1\r\n+second\r\n:2\r\nx03" | netcat -N localhost 7556
```

#### Command Breakdown:
Expand All @@ -40,13 +41,13 @@ echo "*3\r\n\$3\r\nSET\r\n\$9\r\nmycounter\r\n%2\r\n+first\r\n:1\r\n+second\r\n:
#### GET
Retrieve the value of a key. If the key doesn’t exist, `-not found` is returned. GET only accepts strings as keys.
```bash
echo "*2\r\n\$3\r\nGET\r\n\$9\r\nmycounter\r\n" | netcat -N localhost 7556
echo "*2\r\n\$3\r\nGET\r\n\$9\r\nmycounter\r\n\x03" | netcat -N localhost 7556
```

#### PING
Returns `PONG`. This command is often used to test if a connection is still alive, or to measure latency.
```bash
echo "*1\r\n$4\r\nPING\r\n" | netcat -N localhost 7556
echo "*1\r\n\$4\r\nPING\r\n\x03" | netcat -N localhost 7556
```

## Running Tests
Expand Down
13 changes: 13 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ pub fn build(b: *std.Build) void {
// for restricting supported target set are available.
const target = b.standardTargetOptions(.{});

const tls_enabled = b.option(bool, "tls-enabled", "Enable TLS") orelse false;

const options = b.addOptions();
options.addOption(bool, "tls_enabled", tls_enabled);

// Standard optimization options allow the person running `zig build` to select
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not
// set a preferred release mode, allowing the user to decide how to optimize.
Expand All @@ -25,6 +30,14 @@ pub fn build(b: *std.Build) void {
.target = target,
.optimize = optimize,
});

exe.root_module.addOptions("build_options", options);

if (tls_enabled) {
exe.linkSystemLibrary("openssl");
exe.linkSystemLibrary("crypto");
}

exe.linkLibC();

// This declares intent for the executable to be installed into the
Expand Down
3 changes: 2 additions & 1 deletion docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ cd zcached
```
**3. Build the project.**
```bash
zig build run
# provide --tls-enabled=true to build zcached with tls
zig build
```
**4. Run the executable.**
```bash
Expand Down
2 changes: 1 addition & 1 deletion src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ fn handle_arguments(args: cli.Args) ?void {
}

fn run_supervisor(allocator: std.mem.Allocator, context: Employer.Context) void {
context.logger.log(.Info, "# starting zcached server on {?}", .{
context.logger.log(.Info, "# starting zcached server on tcp:{?}", .{
context.config.address,
});

Expand Down
47 changes: 35 additions & 12 deletions src/server/config.zig
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,19 @@ maxmemory: usize = 0, // 0 means unlimited, value in bytes
cbuffer: usize = 4096, // its resized if more space is requied
workers: usize = 4,

secure_transport: bool = false,
cert_path: ?[]const u8 = null,
key_path: ?[]const u8 = null,

whitelist: std.ArrayList(std.net.Address) = undefined,
proto_max_bulk_len: usize = 512 * 1024 * 1024, // 0 means unlimited, value in bytes

allocator: std.mem.Allocator,

pub fn deinit(config: *const Config) void {
config.whitelist.deinit();
if (config.cert_path != null) config.allocator.free(config.cert_path.?);
if (config.key_path != null) config.allocator.free(config.key_path.?);
}

pub fn load(allocator: std.mem.Allocator, file_path: ?[]const u8, log_path: ?[]const u8) !Config {
Expand Down Expand Up @@ -79,6 +85,9 @@ const ConfigField = enum {
proto_max_bulk_len,
workers,
cbuffer,
secure_transport,
cert_path,
key_path,
whitelist,
};

Expand Down Expand Up @@ -155,7 +164,7 @@ fn process_field(
return;
}

const node = root.ast.fields[field_idx - 1];
const node: u32 = root.ast.fields[field_idx - 1];
const info: std.zig.Ast.Node = ast.nodes.get(node);

switch (info.tag) {
Expand All @@ -165,21 +174,29 @@ fn process_field(
try assign_field_value(config, field_name, value.int);
},
std.zig.Ast.Node.Tag.string_literal => {
const Booleans = enum { true, false, True, False };

const value = try utils.parse_string(allocator, ast, field_idx);
defer allocator.free(value);

try assign_field_value(config, field_name, value);
const boolean_result = std.meta.stringToEnum(Booleans, value) orelse {
return try assign_field_value(config, field_name, value);
};
switch (boolean_result) {
.true, .True => try assign_field_value(config, field_name, true),
.false, .False => try assign_field_value(config, field_name, false),
}
},

else => return,
}
}

fn assign_field_value(config: *Config, field_name: []const u8, value: anytype) !void {
const config_field = std.meta.stringToEnum(ConfigField, field_name);
if (config_field == null) return;
const config_field = std.meta.stringToEnum(ConfigField, field_name) orelse {
return;
};

switch (config_field.?) {
switch (config_field) {
.address => {
if (@TypeOf(value) != []const u8) return;

Expand All @@ -195,34 +212,40 @@ fn assign_field_value(config: *Config, field_name: []const u8, value: anytype) !
},
.port => {
if (@TypeOf(value) != u64) return;

config.address.setPort(@as(u16, @truncate(value)));
},
.maxclients => {
if (@TypeOf(value) != u64) return;

config.maxclients = value;
},
.maxmemory => {
if (@TypeOf(value) != u64) return;

config.maxmemory = value;
},
.proto_max_bulk_len => {
if (@TypeOf(value) != u64) return;

config.proto_max_bulk_len = value;
},
.workers => {
if (@TypeOf(value) != u64) return;

config.workers = value;
},
.cbuffer => {
if (@TypeOf(value) != u64) return;

config.cbuffer = value;
},
.secure_transport => {
if (@TypeOf(value) != bool) return;
config.secure_transport = value;
},
.cert_path => {
if (@TypeOf(value) != []const u8) return;
config.cert_path = try config.allocator.dupe(u8, value);
},
.key_path => {
if (@TypeOf(value) != []const u8) return;
config.key_path = try config.allocator.dupe(u8, value);
},
.whitelist => {
if (@TypeOf(value) != std.ArrayList(std.net.Address)) return;

Expand Down
3 changes: 2 additions & 1 deletion src/server/network/connection.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ const std = @import("std");
const assert = std.debug.assert;

const server = @import("stream_server.zig");
const Stream = @import("stream.zig").Stream;

const Connection = @This();

buffer: []u8,
position: usize = 0,
stream: std.net.Stream,
stream: Stream,
address: std.net.Address,

allocator: std.mem.Allocator,
Expand Down
16 changes: 12 additions & 4 deletions src/server/network/listener.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const std = @import("std");

const StreamServer = @import("../network/stream_server.zig");
const Connection = @import("../network/connection.zig");
const Stream = @import("stream.zig").Stream;

const Worker = @import("../processing/worker.zig");
const Context = @import("../processing/employer.zig").Context;
Expand Down Expand Up @@ -90,9 +91,9 @@ pub fn listen(self: *Listener, worker: *Worker) void {
}

// file descriptor is ready for reading.
if (pollfd.revents == std.posix.POLL.IN) {
if (pollfd.revents & (std.posix.POLL.IN | std.posix.POLL.HUP) != 0) {
const connection = worker.states.getPtr(pollfd.fd) orelse continue;
self.handle_connection(worker, connection);
self.handle_connection(worker, connection, i);
continue;
}

Expand All @@ -105,7 +106,7 @@ pub fn listen(self: *Listener, worker: *Worker) void {

const AcceptError = struct {
etype: anyerror,
fd: ?std.net.Stream,
fd: ?Stream,
};
const AcceptResult = union(enum) {
ok: void,
Expand Down Expand Up @@ -205,12 +206,19 @@ fn handle_incoming(self: *Listener, worker: *Worker) AcceptResult {
return .{ .ok = void{} };
}

fn handle_connection(self: *const Listener, worker: *Worker, connection: *Connection) void {
fn handle_connection(self: *Listener, worker: *Worker, connection: *Connection, i: usize) void {
while (true) {
self.on_receive(worker, connection) catch |err| {
switch (err) {
error.WouldBlock => return,
error.NotOpenForReading => return,
error.SocketNotConnected => handle_disconnection(
self,
worker,
connection,
i,
),
error.ConnectionClosed => return,
else => connection.close(),
}
};
Expand Down
Loading

0 comments on commit bcfb93f

Please sign in to comment.