etchdns 0.1.0

A caching DNS proxy with advanced security features, WebAssembly hooks, and comprehensive protection mechanisms
Documentation
const std = @import("std");
const extism_pdk = @import("extism-pdk");
const Plugin = extism_pdk.Plugin;

const allocator = std.heap.wasm_allocator;

/// This function is called when a client query is received.
/// It takes the client IP address and the query name as parameters.
/// Returns 0 to continue processing the query normally, or -1 to return a REFUSED response.
///
/// Parameters:
/// - client_ip: The client's IP address (without port)
/// - query_name: The domain name being queried
export fn hook_client_query_received() i32 {
    const plugin = Plugin.init(allocator);

    // Parse the JSON input
    const Input = struct {
        client_ip: []const u8,
        query_name: []const u8,
    };

    const input = plugin.getJson(Input) catch |err| {
        const error_msg = std.fmt.allocPrint(allocator, "Failed to parse input JSON: {s}", .{@errorName(err)}) catch unreachable;
        plugin.setError(error_msg);
        allocator.free(error_msg);
        return -1;
    };

    // Log the received query for debugging
    const log_msg = std.fmt.allocPrint(allocator, "Received query from {s} for {s}", .{ input.client_ip, input.query_name }) catch unreachable;
    plugin.log(.Info, log_msg);
    allocator.free(log_msg);

    // Check if the query is for a blocked domain
    const blocked_domains = [_][]const u8{
        "example.com",
        "blocked.example.org",
        "malware.example.net",
    };

    for (blocked_domains) |domain| {
        if (isDomainOrSubdomain(input.query_name, domain)) {
            const block_msg = std.fmt.allocPrint(allocator, "Blocking query for {s} from {s}", .{ input.query_name, input.client_ip }) catch unreachable;
            plugin.log(.Warn, block_msg);
            allocator.free(block_msg);

            // Return -1 to indicate that the query should be refused
            return -1;
        }
    }

    // Return 0 to continue processing the query normally
    return 0;
}

/// Checks if a domain is equal to or a subdomain of another domain.
/// For example, "www.example.com" is a subdomain of "example.com".
fn isDomainOrSubdomain(domain: []const u8, parent: []const u8) bool {
    if (std.mem.eql(u8, domain, parent)) {
        return true;
    }

    // Check if domain ends with ".parent"
    const suffix = std.fmt.allocPrint(allocator, ".{s}", .{parent}) catch return false;
    defer allocator.free(suffix);

    return std.mem.endsWith(u8, domain, suffix);
}