/* SPDX-License-Identifier: LGPL-2.1-or-later */

#include <unistd.h>

#include "firewall-util.h"
#include "firewall-util-private.h"
#include "log.h"
#include "random-util.h"
#include "socket-util.h"
#include "tests.h"

static void test_v6(FirewallContext *ctx) {
        union in_addr_union u1, u2, u3;
        uint8_t prefixlen;
        int r;

        assert_se(ctx);

        log_info("/* %s(backend=%s) */", __func__, firewall_backend_to_string(ctx->backend));

        if (!socket_ipv6_is_supported())
                return log_info("IPv6 is not supported by kernel, skipping tests.");

        assert_se(in_addr_from_string(AF_INET6, "dead::beef", &u1) >= 0);
        assert_se(in_addr_from_string(AF_INET6, "1c3::c01d", &u2) >= 0);

        prefixlen = random_u64_range(128 + 1 - 8) + 8;
        pseudo_random_bytes(&u3, sizeof(u3));

        assert_se(fw_add_masquerade(&ctx, true, AF_INET6, &u1, 128) >= 0);
        assert_se(fw_add_masquerade(&ctx, false, AF_INET6, &u1, 128) >= 0);
        assert_se(fw_add_masquerade(&ctx, true, AF_INET6, &u1, 64) >= 0);
        assert_se(fw_add_masquerade(&ctx, false, AF_INET6, &u1, 64) >= 0);
        assert_se(fw_add_masquerade(&ctx, true, AF_INET6, &u3, prefixlen) >= 0);
        assert_se(fw_add_masquerade(&ctx, false, AF_INET6, &u3, prefixlen) >= 0);

        r = fw_add_local_dnat(&ctx, true, AF_INET6, IPPROTO_TCP, 4711, &u1, 815, NULL);
        if (r == -EOPNOTSUPP) {
                log_info("IPv6 DNAT seems not supported, skipping the following tests.");
                return;
        }
        assert_se(r >= 0);

        assert_se(fw_add_local_dnat(&ctx, true, AF_INET6, IPPROTO_TCP, 4711, &u2, 815, &u1) >= 0);
        assert_se(fw_add_local_dnat(&ctx, false, AF_INET6, IPPROTO_TCP, 4711, &u2, 815, NULL) >= 0);

}

static union in_addr_union *parse_addr(const char *str, union in_addr_union *u) {
        assert_se(str);
        assert_se(u);
        assert_se(in_addr_from_string(AF_INET, str, u) >= 0);
        return u;
}

static bool test_v4(FirewallContext *ctx) {
        union in_addr_union u, v;
        int r;

        assert_se(ctx);

        log_info("/* %s(backend=%s) */", __func__, firewall_backend_to_string(ctx->backend));

#if HAVE_LIBIPTC
        if (ctx->backend == FW_BACKEND_IPTABLES && fw_iptables_init_nat(NULL) < 0) {
                log_debug("iptables backend is used, but nat table is not enabled, skipping tests");
                return false;
        }
#endif

        assert_se(fw_add_masquerade(&ctx, true, AF_INET, NULL, 0) == -EINVAL);
        assert_se(fw_add_masquerade(&ctx, true, AF_INET, parse_addr("10.1.2.0", &u), 0) == -EINVAL);

        r = fw_add_masquerade(&ctx, true, AF_INET, parse_addr("10.1.2.3", &u), 32);
        if (r < 0) {
                bool ignore = IN_SET(r, -EPERM, -EOPNOTSUPP, -ENOPROTOOPT);

                log_full_errno(ignore ? LOG_DEBUG : LOG_ERR, r,
                               "Failed to add IPv4 masquerade%s: %m",
                               ignore ? ", skipping following tests" : "");

                if (ignore)
                        return false;
        }
        assert_se(r >= 0);

        assert_se(fw_add_masquerade(&ctx, true, AF_INET, parse_addr("10.0.2.0", &u), 28) >= 0);
        assert_se(fw_add_masquerade(&ctx, false, AF_INET, parse_addr("10.0.2.0", &u), 28) >= 0);
        assert_se(fw_add_masquerade(&ctx, false, AF_INET, parse_addr("10.1.2.3", &u), 32) >= 0);
        assert_se(fw_add_local_dnat(&ctx, true, AF_INET, IPPROTO_TCP, 4711, parse_addr("1.2.3.4", &u), 815, NULL) >= 0);
        assert_se(fw_add_local_dnat(&ctx, true, AF_INET, IPPROTO_TCP, 4711, parse_addr("1.2.3.4", &u), 815, NULL) >= 0);
        assert_se(fw_add_local_dnat(&ctx, true, AF_INET, IPPROTO_TCP, 4711, parse_addr("1.2.3.5", &u), 815, parse_addr("1.2.3.4", &v)) >= 0);
        assert_se(fw_add_local_dnat(&ctx, false, AF_INET, IPPROTO_TCP, 4711, parse_addr("1.2.3.5", &u), 815, NULL) >= 0);

        return true;
}

int main(int argc, char *argv[]) {
        _cleanup_(fw_ctx_freep) FirewallContext *ctx = NULL;

        test_setup_logging(LOG_DEBUG);

        if (getuid() != 0)
                return log_tests_skipped("not root");

        assert_se(fw_ctx_new(&ctx) >= 0);
        assert_se(ctx);

        if (ctx->backend == FW_BACKEND_NONE)
                return EXIT_TEST_SKIP;

        if (test_v4(ctx) && ctx->backend == FW_BACKEND_NFTABLES)
                test_v6(ctx);

#if HAVE_LIBIPTC
        if (ctx->backend != FW_BACKEND_IPTABLES) {
                ctx->backend = FW_BACKEND_IPTABLES;
                test_v4(ctx);
        }
#endif

        return 0;
}
