diff --git a/src/knot/modules/geoip/geoip.c b/src/knot/modules/geoip/geoip.c index a196b5015c8481997d3c0aa7f99097f1ddcfdba7..1da2d71166eb0b587390c241795b3c7840506ac3 100644 --- a/src/knot/modules/geoip/geoip.c +++ b/src/knot/modules/geoip/geoip.c @@ -69,6 +69,15 @@ const yp_item_t geoip_conf[] = { { NULL } }; +char geoip_check_str[1024]; + +typedef struct { + knotd_conf_check_args_t *args; // Set for a dry run. + knotd_mod_t *mod; // Set for a real module load. +} check_ctx_t; + +static int load_module(check_ctx_t *ctx); + int geoip_conf_check(knotd_conf_check_args_t *args) { knotd_conf_t conf = knotd_conf_check_item(args, MOD_CONFIG_FILE); @@ -103,7 +112,9 @@ int geoip_conf_check(knotd_conf_check_args_t *args) } knotd_conf_free(&conf); } - return KNOT_EOK; + + check_ctx_t check = { .args = args }; + return load_module(&check); } typedef struct { @@ -269,7 +280,33 @@ static int add_view_to_trie(knot_dname_t *owner, geo_view_t *view, geoip_ctx_t * return ret; } -static int finalize_geo_view(knotd_mod_t *mod, geo_view_t *view, knot_dname_t *owner, +static void geo_log(check_ctx_t *check, int priority, const char *fmt, ...) +{ + va_list vargs; + va_start(vargs, fmt); + + if (check->args != NULL) { + if (vsnprintf(geoip_check_str, sizeof(geoip_check_str), fmt, vargs) < 0) { + geoip_check_str[0] = '\0'; + } + check->args->err_str = geoip_check_str; + } else { + knotd_mod_vlog(check->mod, priority, fmt, vargs); + } + + va_end(vargs); +} + +static knotd_conf_t geo_conf(check_ctx_t *check, const yp_name_t *item_name) +{ + if (check->args != NULL) { + return knotd_conf_check_item(check->args, item_name); + } else { + return knotd_conf_mod(check->mod, item_name); + } +} + +static int finalize_geo_view(check_ctx_t *check, geo_view_t *view, knot_dname_t *owner, geoip_ctx_t *ctx) { if (view == NULL || view->count == 0) { @@ -278,6 +315,7 @@ static int finalize_geo_view(knotd_mod_t *mod, geo_view_t *view, knot_dname_t *o int ret = KNOT_EOK; if (ctx->dnssec) { + assert(check->mod != NULL); view->rrsigs = malloc(sizeof(knot_rrset_t) * view->count); if (view->rrsigs == NULL) { return KNOT_ENOMEM; @@ -289,7 +327,7 @@ static int finalize_geo_view(knotd_mod_t *mod, geo_view_t *view, knot_dname_t *o } knot_rrset_init(&view->rrsigs[i], owner_cpy, KNOT_RRTYPE_RRSIG, KNOT_CLASS_IN, ctx->ttl); - ret = knotd_mod_dnssec_sign_rrset(mod, &view->rrsigs[i], + ret = knotd_mod_dnssec_sign_rrset(check->mod, &view->rrsigs[i], &view->rrsets[i], NULL); if (ret != KNOT_EOK) { return ret; @@ -364,7 +402,7 @@ static int parse_origin(yp_parser_t *yp, zs_scanner_t *scanner) return KNOT_EOK; } -static int parse_view(knotd_mod_t *mod, geoip_ctx_t *ctx, yp_parser_t *yp, geo_view_t *view) +static int parse_view(check_ctx_t *check, geoip_ctx_t *ctx, yp_parser_t *yp, geo_view_t *view) { // Initialize new geo view. memset(view, 0, sizeof(*view)); @@ -376,8 +414,8 @@ static int parse_view(knotd_mod_t *mod, geoip_ctx_t *ctx, yp_parser_t *yp, geo_v // Check view type syntax. int key_len = strlen(mode_key[ctx->mode]); if (yp->key_len != key_len || memcmp(yp->key, mode_key[ctx->mode], key_len) != 0) { - knotd_mod_log(mod, LOG_ERR, "invalid key type (%s) on line %zu", - yp->key, yp->line_count); + geo_log(check, LOG_ERR, "invalid key type '%s' on line %zu", + yp->key, yp->line_count); return KNOT_EINVAL; } @@ -385,8 +423,8 @@ static int parse_view(knotd_mod_t *mod, geoip_ctx_t *ctx, yp_parser_t *yp, geo_v if (ctx->mode == MODE_GEODB) { if (parse_geodb_data((char *)yp->data, view->geodata, view->geodata_len, &view->geodepth, ctx->paths, ctx->path_count) != 0) { - knotd_mod_log(mod, LOG_ERR, "invalid geo format (%s) on line %zu", - yp->data, yp->line_count); + geo_log(check, LOG_ERR, "invalid geo format '%s' on line %zu", + yp->data, yp->line_count); return KNOT_EINVAL; } } else if (ctx->mode == MODE_SUBNET) { @@ -411,8 +449,8 @@ static int parse_view(knotd_mod_t *mod, geoip_ctx_t *ctx, yp_parser_t *yp, geo_v view->subnet_prefix = 128; } if (ret != KNOT_EOK) { - knotd_mod_log(mod, LOG_ERR, "invalid address format (%s) on line %zu", - yp->data, yp->line_count); + geo_log(check, LOG_ERR, "invalid address format '%s' on line %zu", + yp->data, yp->line_count); return KNOT_EINVAL; } @@ -420,27 +458,27 @@ static int parse_view(knotd_mod_t *mod, geoip_ctx_t *ctx, yp_parser_t *yp, geo_v if (slash < yp->data + yp->data_len - 1) { ret = str_to_u8(slash + 1, &view->subnet_prefix); if (ret != KNOT_EOK) { - knotd_mod_log(mod, LOG_ERR, "invalid prefix (%s) on line %zu", - slash + 1, yp->line_count); + geo_log(check, LOG_ERR, "invalid prefix '%s' on line %zu", + slash + 1, yp->line_count); return ret; } if (view->subnet->ss_family == AF_INET && view->subnet_prefix > 32) { view->subnet_prefix = 32; - knotd_mod_log(mod, LOG_WARNING, "IPv4 prefix too large on line %zu, set to 32", - yp->line_count); + geo_log(check, LOG_WARNING, "IPv4 prefix too large on line %zu, set to 32", + yp->line_count); } if (view->subnet->ss_family == AF_INET6 && view->subnet_prefix > 128) { view->subnet_prefix = 128; - knotd_mod_log(mod, LOG_WARNING, "IPv6 prefix too large on line %zu, set to 128", - yp->line_count); + geo_log(check, LOG_WARNING, "IPv6 prefix too large on line %zu, set to 128", + yp->line_count); } } } else if (ctx->mode == MODE_WEIGHTED) { uint8_t weight; ret = str_to_u8(yp->data, &weight); if (ret != KNOT_EOK) { - knotd_mod_log(mod, LOG_ERR, "invalid weight (%s) on line %zu", - yp->data, yp->line_count); + geo_log(check, LOG_ERR, "invalid weight '%s' on line %zu", + yp->data, yp->line_count); return ret; } view->weight = weight; @@ -449,31 +487,31 @@ static int parse_view(knotd_mod_t *mod, geoip_ctx_t *ctx, yp_parser_t *yp, geo_v return KNOT_EOK; } -static int parse_rr(knotd_mod_t *mod, yp_parser_t *yp, zs_scanner_t *scanner, +static int parse_rr(check_ctx_t *check, yp_parser_t *yp, zs_scanner_t *scanner, knot_dname_t *owner, geo_view_t *view, uint32_t ttl) { uint16_t rr_type = KNOT_RRTYPE_A; if (knot_rrtype_from_string(yp->key, &rr_type) != 0) { - knotd_mod_log(mod, LOG_ERR, "invalid RR type (%s) on line %zu", - yp->key, yp->line_count); + geo_log(check, LOG_ERR, "invalid RR type '%s' on line %zu", + yp->key, yp->line_count); return KNOT_EINVAL; } if (rr_type == KNOT_RRTYPE_CNAME && view->count > 0) { - knotd_mod_log(mod, LOG_ERR, "cannot add CNAME to view with other RRs on line %zu", - yp->line_count); + geo_log(check, LOG_ERR, "cannot add CNAME to view with other RRs on line %zu", + yp->line_count); return KNOT_EINVAL; } if (view->cname != NULL) { - knotd_mod_log(mod, LOG_ERR, "cannot add RR to view with CNAME on line %zu", - yp->line_count); + geo_log(check, LOG_ERR, "cannot add RR to view with CNAME on line %zu", + yp->line_count); return KNOT_EINVAL; } if (knot_rrtype_is_dnssec(rr_type)) { - knotd_mod_log(mod, LOG_ERR, "DNSSEC record (%s) not allowed on line %zu", - yp->key, yp->line_count); + geo_log(check, LOG_ERR, "DNSSEC record '%s' not allowed on line %zu", + yp->key, yp->line_count); return KNOT_EINVAL; } @@ -525,7 +563,7 @@ static int parse_rr(knotd_mod_t *mod, yp_parser_t *yp, zs_scanner_t *scanner, return knot_rrset_add_rdata(add_rr, scanner->r_data, scanner->r_data_length, NULL); } -static int geo_conf_yparse(knotd_mod_t *mod, geoip_ctx_t *ctx) +static int geo_conf_yparse(check_ctx_t *check, geoip_ctx_t *ctx) { int ret = KNOT_EOK; yp_parser_t *yp = NULL; @@ -544,10 +582,11 @@ static int geo_conf_yparse(knotd_mod_t *mod, geoip_ctx_t *ctx) goto cleanup; } yp_init(yp); - knotd_conf_t conf = knotd_conf_mod(mod, MOD_CONFIG_FILE); + knotd_conf_t conf = geo_conf(check, MOD_CONFIG_FILE); ret = yp_set_input_file(yp, conf.single.string); if (ret != KNOT_EOK) { - knotd_mod_log(mod, LOG_ERR, "failed to load configuration file"); + geo_log(check, LOG_ERR, "failed to load module config file '%s' (%s)", + conf.single.string, knot_strerror(ret)); goto cleanup; } @@ -567,18 +606,19 @@ static int geo_conf_yparse(knotd_mod_t *mod, geoip_ctx_t *ctx) // Get the next item in config. ret = yp_parse(yp); if (ret == KNOT_EOF) { - ret = finalize_geo_view(mod, view, owner, ctx); + ret = finalize_geo_view(check, view, owner, ctx); goto cleanup; } if (ret != KNOT_EOK) { - knotd_mod_log(mod, LOG_ERR, "failed to parse configuration file (%s)", - knot_strerror(ret)); + geo_log(check, LOG_ERR, + "failed to parse module config file on line %zu (%s)", + yp->line_count, knot_strerror(ret)); goto cleanup; } // If the next item is not a rrset, the current view is finished. if (yp->event != YP_EKEY1) { - ret = finalize_geo_view(mod, view, owner, ctx); + ret = finalize_geo_view(check, view, owner, ctx); if (ret != KNOT_EOK) { goto cleanup; } @@ -588,8 +628,9 @@ static int geo_conf_yparse(knotd_mod_t *mod, geoip_ctx_t *ctx) if (yp->event == YP_EKEY0) { owner = knot_dname_from_str(owner_buff, yp->key, sizeof(owner_buff)); if (owner == NULL) { - knotd_mod_log(mod, LOG_ERR, "invalid domain name in config on line %zu", - yp->line_count); + geo_log(check, LOG_ERR, + "invalid domain name in module config file on line %zu", + yp->line_count); ret = KNOT_EINVAL; goto cleanup; } @@ -601,7 +642,7 @@ static int geo_conf_yparse(knotd_mod_t *mod, geoip_ctx_t *ctx) // Next view. if (yp->event == YP_EID) { - ret = parse_view(mod, ctx, yp, view); + ret = parse_view(check, ctx, yp, view); if (ret != KNOT_EOK) { goto cleanup; } @@ -616,12 +657,13 @@ static int geo_conf_yparse(knotd_mod_t *mod, geoip_ctx_t *ctx) [MODE_GEODB] = "- geo: LOCATION", [MODE_WEIGHTED] = "- weight: WEIGHT" }; - knotd_mod_log(mod, LOG_ERR, "missing '%s' in config before line %zu", - err_str[ctx->mode], yp->line_count); + geo_log(check, LOG_ERR, + "missing '%s' in module config file before line %zu", + err_str[ctx->mode], yp->line_count); ret = KNOT_EINVAL; goto cleanup; } - ret = parse_rr(mod, yp, scanner, owner, view, ctx->ttl); + ret = parse_rr(check, yp, scanner, owner, view, ctx->ttl); if (ret != KNOT_EOK) { goto cleanup; } @@ -901,17 +943,20 @@ static knotd_in_state_t geoip_process(knotd_in_state_t state, knot_pkt_t *pkt, } } -int geoip_load(knotd_mod_t *mod) +static int load_module(check_ctx_t *check) { + assert((check->args != NULL) != (check->mod != NULL)); + knotd_mod_t *mod = check->mod; + // Create module context. geoip_ctx_t *ctx = calloc(1, sizeof(geoip_ctx_t)); if (ctx == NULL) { return KNOT_ENOMEM; } - knotd_conf_t conf = knotd_conf_mod(mod, MOD_TTL); + knotd_conf_t conf = geo_conf(check, MOD_TTL); ctx->ttl = conf.single.integer; - conf = knotd_conf_mod(mod, MOD_MODE); + conf = geo_conf(check, MOD_MODE); ctx->mode = conf.single.option; // Initialize the dname trie. @@ -923,16 +968,16 @@ int geoip_load(knotd_mod_t *mod) if (ctx->mode == MODE_GEODB) { // Initialize geodb. - conf = knotd_conf_mod(mod, MOD_GEODB_FILE); + conf = geo_conf(check, MOD_GEODB_FILE); ctx->geodb = geodb_open(conf.single.string); if (ctx->geodb == NULL) { - knotd_mod_log(mod, LOG_ERR, "failed to open geo DB"); + geo_log(check, LOG_ERR, "failed to open geo DB"); free_geoip_ctx(ctx); return KNOT_EINVAL; } // Load configured geodb keys. - conf = knotd_conf_mod(mod, MOD_GEODB_KEY); + conf = geo_conf(check, MOD_GEODB_KEY); assert(conf.count <= GEODB_MAX_DEPTH); ctx->path_count = conf.count; for (size_t i = 0; i < conf.count; i++) { @@ -941,39 +986,55 @@ int geoip_load(knotd_mod_t *mod) knotd_conf_free(&conf); } - // Is DNSSEC used on this zone? - conf = knotd_conf_mod(mod, MOD_DNSSEC); - if (conf.count == 0) { - conf = knotd_conf_zone(mod, C_DNSSEC_SIGNING, knotd_mod_zone(mod)); - } - ctx->dnssec = conf.single.boolean; - if (ctx->dnssec) { - int ret = knotd_mod_dnssec_init(mod); - if (ret != KNOT_EOK) { - knotd_mod_log(mod, LOG_ERR, "failed to initialize DNSSEC"); - free_geoip_ctx(ctx); - return ret; + if (mod != NULL) { + // Is DNSSEC used on this zone? + conf = knotd_conf_mod(mod, MOD_DNSSEC); + if (conf.count == 0) { + conf = knotd_conf_zone(mod, C_DNSSEC_SIGNING, knotd_mod_zone(mod)); } - ret = knotd_mod_dnssec_load_keyset(mod, false); - if (ret != KNOT_EOK) { - knotd_mod_log(mod, LOG_ERR, "failed to load DNSSEC keys"); - free_geoip_ctx(ctx); - return ret; + ctx->dnssec = conf.single.boolean; + if (ctx->dnssec) { + int ret = knotd_mod_dnssec_init(mod); + if (ret != KNOT_EOK) { + knotd_mod_log(mod, LOG_ERR, "failed to initialize DNSSEC"); + free_geoip_ctx(ctx); + return ret; + } + ret = knotd_mod_dnssec_load_keyset(mod, false); + if (ret != KNOT_EOK) { + knotd_mod_log(mod, LOG_ERR, "failed to load DNSSEC keys"); + free_geoip_ctx(ctx); + return ret; + } } } // Parse geo configuration file. - int ret = geo_conf_yparse(mod, ctx); + int ret = geo_conf_yparse(check, ctx); if (ret != KNOT_EOK) { - knotd_mod_log(mod, LOG_ERR, "failed to load geo configuration"); free_geoip_ctx(ctx); return ret; } - // Prepare geo views for faster search. - geo_sort_and_link(ctx); + if (mod != NULL) { + // Prepare geo views for faster search. + geo_sort_and_link(ctx); - knotd_mod_ctx_set(mod, ctx); + knotd_mod_ctx_set(mod, ctx); + } else { + free_geoip_ctx(ctx); + } + + return ret; +} + +int geoip_load(knotd_mod_t *mod) +{ + check_ctx_t check = { .mod = mod }; + int ret = load_module(&check); + if (ret != KNOT_EOK) { + return ret; + } return knotd_mod_in_hook(mod, KNOTD_STAGE_PREANSWER, geoip_process); }