Hi there!
At work, we make use something often referred to as "weighted records",
a feature offered by many managed DNS vendors. We use it to implement
e.g. a canary environment, where we test changes on a small portion of
production traffic.
I played around with knot-dns for a bit (quite impressed), and figured
it might be nice to implement such a feature for it. Turns out, the code
is so amazing that if you abuse the infrastructure of the geoip module,
it only takes an hour (very impressed).
If you are interested in trying it, read on further below, but just to
state my intention: I was wondering if such a feature would be of
interest at all?
I realize the geoip module may not be the appropriate for such an
implementation, consider this merely a demo of sorts.
So here goes nothing:
1. apply attached patch
2. config file snippet:
mod-geoip:
- id: test
config-file: /etc/knot/test.conf
ttl: 600
mode: weighted
zone:
- domain: example.com.
file: "/var/lib/knot/example.com.zone"
module: mod-geoip/test
3. /etc/knot/test.conf:
lb.example.com:
- weight: 10
CNAME: www1.example.com.
- weight: 5
CNAME: www2.example.com.
Results in this:
conrad@deltree ~/hack/knot-dns $ for i in $(seq 1 100); do dig
@192.168.1.242 A lb.example.com +short; done | sort | uniq -c
68 www1.example.com.
32 www2.example.com.
conrad@deltree ~/hack/knot-dns $ for i in $(seq 1 100); do dig
@192.168.1.242 A lb.example.com +short; done | sort | uniq -c
72 www1.example.com.
28 www2.example.com.
conrad@deltree ~/hack/knot-dns $ for i in $(seq 1 100); do dig
@192.168.1.242 A lb.example.com +short; done | sort | uniq -c
66 www1.example.com.
34 www2.example.com.
You get the idea. Anyways, any thoughts and feedback would be greatly
appreciated.
Thanks a lot,
Conrad
diff --git a/src/knot/modules/geoip/geoip.c b/src/knot/modules/geoip/geoip.c
index 2f90295b4..c69907535 100644
--- a/src/knot/modules/geoip/geoip.c
+++ b/src/knot/modules/geoip/geoip.c
@@ -27,6 +27,7 @@
#include "contrib/sockaddr.h"
#include "contrib/string.h"
#include "contrib/strtonum.h"
+#include "libdnssec/random.h"
#include "libzscanner/scanner.h"
#define MOD_CONFIG_FILE "\x0B""config-file"
@@ -37,18 +38,21 @@
enum operation_mode {
MODE_SUBNET,
- MODE_GEODB
+ MODE_GEODB,
+ MODE_WEIGHTED
};
static const knot_lookup_t modes[] = {
- { MODE_SUBNET, "subnet" },
- { MODE_GEODB, "geodb" },
+ { MODE_SUBNET, "subnet" },
+ { MODE_GEODB, "geodb" },
+ { MODE_WEIGHTED, "weighted" },
{ 0, NULL }
};
static const char* mode_key[] = {
- [MODE_SUBNET] = "net",
- [MODE_GEODB] = "geo"
+ [MODE_SUBNET] = "net",
+ [MODE_GEODB] = "geo",
+ [MODE_WEIGHTED] = "weight"
};
const yp_item_t geoip_conf[] = {
@@ -101,6 +105,8 @@ typedef struct {
uint32_t geodata_len[GEODB_MAX_DEPTH];
uint8_t geodepth;
+ uint8_t weight;
+
size_t count, avail;
knot_rrset_t *rrsets;
knot_rrset_t *rrsigs;
@@ -260,7 +266,7 @@ static int parse_view(knotd_mod_t *mod, geoip_ctx_t *ctx, yp_parser_t *yp, geo_v
return KNOT_EINVAL;
}
- // Parse geodata/subnet.
+ // Parse geodata/subnet/weight.
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) {
@@ -314,6 +320,13 @@ static int parse_view(knotd_mod_t *mod, geoip_ctx_t *ctx, yp_parser_t *yp, geo_v
yp->line_count);
}
}
+ } else if (ctx->mode == MODE_WEIGHTED) {
+ ret = str_to_u8(yp->data, &view->weight);
+ if (ret != KNOT_EOK) {
+ knotd_mod_log(mod, LOG_ERR, "invalid weight (%s) on line %zu",
+ yp->data, yp->line_count);
+ return ret;
+ }
}
return KNOT_EOK;
@@ -618,6 +631,31 @@ static knotd_in_state_t geoip_process(knotd_in_state_t state, knot_pkt_t *pkt,
}
}
}
+ } else if (ctx->mode == MODE_WEIGHTED) {
+ uint32_t total_w = 0;
+ for (int i = 0; i < data->count; i++) {
+ geo_view_t *view = &data->views[i];
+ total_w += view->weight;
+ }
+ uint32_t r = dnssec_random_uint32_t() % total_w;
+ for (int i = 0; i < data->count; i++) {
+ geo_view_t *view = &data->views[i];
+ if (r < view->weight) {
+ for (int j = 0; j < view->count; j++) {
+ if (view->rrsets[j].type == qtype) {
+ rr = &view->rrsets[j];
+ rrsig = (view->rrsigs) ? &view->rrsigs[j] : NULL;
+ break;
+ } else if (view->rrsets[j].type == KNOT_RRTYPE_CNAME) {
+ cname = &view->rrsets[j];
+ cnamesig = (view->rrsigs) ? &view->rrsigs[j] : NULL;
+ }
+ }
+ break;
+ } else {
+ r -= view->weight;
+ }
+ }
}
// Return CNAME if only CNAME is found.
--
https://lists.nic.cz/cgi-bin/mailman/listinfo/knot-dns-users