Skip to content

Commit

Permalink
lightningd/runes: Added last_time_check for validating rune for per
Browse files Browse the repository at this point in the history
… restriction

Changelog-Added: runes: `per=Nsec/min/hour/msec/usec/nsec` for general ratelimiting
  • Loading branch information
ShahanaFarooqui authored and rustyrussell committed Sep 20, 2023
1 parent c0f3e3f commit 9a5e06d
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 4 deletions.
1 change: 1 addition & 0 deletions doc/lightning-createrune.7.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ being run:
* id: the node\_id of the peer, e.g. "id=024b9a1fa8e006f1e3937f65f66c408e6da8e1ca728ea43222a7381df1cc449605".
* method: the command being run, e.g. "method=withdraw".
* rate: the rate limit, per minute, e.g. "rate=60".
* per: how often the rune can be used, with suffix "sec" (default), "min", "hour", "day" or "msec", "usec" or "nsec". e.g. "per=5sec".
* pnum: the number of parameters. e.g. "pnum<2".
* pnameX: the parameter named X (with any punctuation like `_` removed). e.g. "pnamedestination=1RustyRX2oai4EYYDpQGWvEL62BBGqN9T".
* parrN: the N'th parameter. e.g. "parr0=1RustyRX2oai4EYYDpQGWvEL62BBGqN9T".
Expand Down
65 changes: 61 additions & 4 deletions lightningd/runes.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <common/json_param.h>
#include <common/json_stream.h>
#include <common/memleak.h>
#include <common/overflows.h>
#include <common/timeout.h>
#include <common/type_to_string.h>
#include <db/exec.h>
Expand Down Expand Up @@ -45,6 +46,7 @@ struct cond_info {
const struct node_id *peer;
const char *buf;
const char *method;
struct timeabs now;
const jsmntok_t *params;
STRMAP(const jsmntok_t *) cached_params;
struct usage *usage;
Expand Down Expand Up @@ -114,6 +116,58 @@ u64 rune_unique_id(const struct rune *rune)
return num;
}

static const char *last_time_check(const tal_t *ctx,
const struct runes *runes,
const struct rune *rune,
const struct rune_altern *alt,
struct cond_info *cinfo)
{
u64 r, multiplier, diff;
struct timeabs last_used;
char *endp;
const u64 sec_per_nsec = 1000000000;

if (alt->condition != '=')
return "per operator must be =";

r = strtoul(alt->value, &endp, 10);
if (endp == alt->value || r == 0 || r >= UINT32_MAX)
return "malformed per";
if (streq(endp, "") || streq(endp, "sec")) {
multiplier = sec_per_nsec;
} else if (streq(endp, "nsec")) {
multiplier = 1;
} else if (streq(endp, "usec")) {
multiplier = 1000;
} else if (streq(endp, "msec")) {
multiplier = 1000000;
} else if (streq(endp, "min")) {
multiplier = 60 * sec_per_nsec;
} else if (streq(endp, "hour")) {
multiplier = 60 * 60 * sec_per_nsec;
} else if (streq(endp, "day")) {
multiplier = 24 * 60 * 60 * sec_per_nsec;
} else {
return "malformed suffix";
}
if (mul_overflows_u64(r, multiplier)) {
return "per overflow";
}
if (!wallet_get_rune(tmpctx, cinfo->runes->ld->wallet, atol(rune->unique_id), &last_used)) {
/* FIXME: If we do not know the rune, per does not work */
return NULL;
}
if (time_before(cinfo->now, last_used)) {
last_used = cinfo->now;
}
diff = time_to_nsec(time_between(cinfo->now, last_used));

if (diff < (r * multiplier)) {
return "too soon";
}
return NULL;
}

static const char *rate_limit_check(const tal_t *ctx,
const struct runes *runes,
const struct rune *rune,
Expand Down Expand Up @@ -722,7 +776,7 @@ static const char *check_condition(const tal_t *ctx,
const jsmntok_t *ptok;

if (streq(alt->fieldname, "time")) {
return rune_alt_single_int(ctx, alt, time_now().ts.tv_sec);
return rune_alt_single_int(ctx, alt, cinfo->now.ts.tv_sec);
} else if (streq(alt->fieldname, "id")) {
if (cinfo->peer) {
const char *id = node_id_to_hexstr(tmpctx, cinfo->peer);
Expand All @@ -739,6 +793,8 @@ static const char *check_condition(const tal_t *ctx,
return rune_alt_single_int(ctx, alt, (cinfo && cinfo->params) ? cinfo->params->size : 0);
} else if (streq(alt->fieldname, "rate")) {
return rate_limit_check(ctx, cinfo->runes, rune, alt, cinfo);
} else if (streq(alt->fieldname, "per")) {
return last_time_check(ctx, cinfo->runes, rune, alt, cinfo);
}

/* Rest are params looksup: generate this once! */
Expand Down Expand Up @@ -791,9 +847,9 @@ static const char *check_condition(const tal_t *ctx,
}

static void update_rune_usage_time(struct runes *runes,
struct rune *rune)
struct rune *rune, struct timeabs now)
{
wallet_rune_update_last_used(runes->ld->wallet, rune, time_now());
wallet_rune_update_last_used(runes->ld->wallet, rune, now);
}

static struct command_result *json_checkrune(struct command *cmd,
Expand Down Expand Up @@ -824,6 +880,7 @@ static struct command_result *json_checkrune(struct command *cmd,
cinfo.buf = buffer;
cinfo.method = method;
cinfo.params = methodparams;
cinfo.now = time_now();
/* We will populate it in rate_limit_check if required. */
cinfo.usage = NULL;
strmap_init(&cinfo.cached_params);
Expand All @@ -845,7 +902,7 @@ static struct command_result *json_checkrune(struct command *cmd,
/* If it succeeded, *now* we increment any associated usage counter. */
if (cinfo.usage)
cinfo.usage->counter++;
update_rune_usage_time(cmd->ld->runes, ras->rune);
update_rune_usage_time(cmd->ld->runes, ras->rune, cinfo.now);

js = json_stream_success(cmd);
json_add_bool(js, "valid", true);
Expand Down

0 comments on commit 9a5e06d

Please sign in to comment.