From 0adf71d6bcdd22dc2b6fe7d8ff7e9a9645c0fab1 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 23 Sep 2023 11:24:37 +0200 Subject: [PATCH 1/7] Add pihole-FTL --perf Signed-off-by: DL6ER --- src/args.c | 12 ++++ src/config/password.c | 142 ++++++++++++++++++++++++++++++++++++++++++ src/config/password.h | 1 + 3 files changed, 155 insertions(+) diff --git a/src/args.c b/src/args.c index 3f90d761..877b0bc9 100644 --- a/src/args.c +++ b/src/args.c @@ -58,6 +58,8 @@ #include "tools/dhcp-discover.h" // run_arp_scan() #include "tools/arp-scan.h" +// run_performance_test() +#include "config/password.h" // defined in dnsmasq.c extern void print_dnsmasq_version(const char *yellow, const char *green, const char *bold, const char *normal); @@ -355,6 +357,14 @@ void parse_args(int argc, char* argv[]) exit(run_dhcp_discover()); } + // Password hashing performance test + if(argc > 1 && (strcmp(argv[1], "--perf") == 0 || strcmp(argv[1], "performance") == 0)) + { + // Enable stdout printing + cli_mode = true; + exit(run_performance_test()); + } + // ARP scanning mode if(argc > 1 && strcmp(argv[1], "arp-scan") == 0) { @@ -817,6 +827,8 @@ void parse_args(int argc, char* argv[]) printf("\t interfaces and scan 10x more often\n"); printf("\t%s--totp%s Generate valid TOTP token for 2FA\n", green, normal); printf("\t authentication (if enabled)\n"); + printf("\t%s--perf%s Run performance-tests based on the\n", green, normal); + printf("\t BALLOON password-hashing algorithm\n"); printf("\t%s--%s [OPTIONS]%s Pass OPTIONS to internal dnsmasq resolver\n", green, cyan, normal); printf("\t%s-h%s, %shelp%s Display this help and exit\n\n", green, normal, green, normal); exit(EXIT_SUCCESS); diff --git a/src/config/password.c b/src/config/password.c index ebbe3caa..edd6cc10 100644 --- a/src/config/password.c +++ b/src/config/password.c @@ -364,3 +364,145 @@ bool verify_password(const char *password, const char* pwhash) return result; } } + +static double sqroot(double square) +{ + double root = square / 3.0; + if (square <= 0) return 0.0; + for (unsigned int i=0; i<32; i++) + root = (root + square / root) / 2; + return root; +} + +static int performance_test_task(const size_t s_cost, const size_t t_cost, const uint8_t password[], const size_t pwlen, uint8_t salt[SALT_LEN], double *avg_sum, size_t *t_costs, size_t *s_costs) +{ + struct timespec start, end, end2; + // Scratch buffer scratch is a user allocated working space required by + // the algorithm. To determine the required size of the scratch buffer + // use the utility function balloon_itch. Output of BALLOON algorithm + // will be written into the output buffer dst that has to be at least + // digest_size bytes long. + const size_t scratch_size = balloon_itch(SHA256_DIGEST_SIZE, s_cost); + uint8_t *scratch = calloc(scratch_size, sizeof(uint8_t)); + if(scratch == NULL) + { + printf("Could not allocate %zu bytes of memory for test!\n", scratch_size); + return -1; + } + + // Record starting time + clock_gettime(CLOCK_MONOTONIC, &start); + + // Compute hash of given password password salted with salt and write + // the result into the output buffer dst + balloon_sha256(s_cost, t_cost, pwlen, password, SALT_LEN, salt, scratch, scratch); + + // Record end time + clock_gettime(CLOCK_MONOTONIC, &end); + + // Compute hash of given password password salted with salt and write + // the result into the output buffer dst + balloon_sha256(s_cost, t_cost, pwlen, password, SALT_LEN, salt, scratch, scratch); + + // Record end time + clock_gettime(CLOCK_MONOTONIC, &end2); + + // Free allocated memory + free(scratch); + + // Compute elapsed time + const double elapsed = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1000000000.0; + const double elapsed2 = (end2.tv_sec - end.tv_sec) + (end2.tv_nsec - end.tv_nsec) / 1000000000.0; + char prefix[2] = { 0 }; + double formatted = 0.0; + format_memory_size(prefix, (unsigned long long)scratch_size, &formatted); + const double avg = (elapsed + elapsed2)/2; + *avg_sum += avg; + *t_costs += t_cost; + *s_costs += s_cost; + const double stdev = sqroot(((elapsed - avg)*(elapsed - avg) + (elapsed2 - avg)*(elapsed2 - avg))/2); + printf("Balloon with s = %zu, t = %zu took %.1f +/- %.1f milliseconds (scratch buffer %.1f%sB)\n", s_cost, t_cost, 1e3*avg, 1e3*stdev, formatted, prefix); + + // Break if test took longer than two seconds + if(elapsed > 2) + return 1; + return 0; +} + +// Run performance tests until individual test result gets beyond 3 seconds +int run_performance_test(void) +{ + struct timespec start, end; + // Record starting time + clock_gettime(CLOCK_MONOTONIC, &start); + + // The space parameter s_cost determines how many blocks of working + // space the algorithm will require during its computation. It is + // common to set s_cost to a high value in order to increase the cost of + // hardware accelerators built by the adversary. + // The algorithm will need (s_cost + 1) * digest_size + size_t s_t_cost, s_s_cost; + + // The time parameter t_cost determines the number of rounds of + // computation that the algorithm will perform. This can be used to + // further increase the cost of computation without raising the memory + // requirement. + size_t t_t_cost, t_s_cost; + + // Test password + const uint8_t password[] = "abcdefghijklmnopqrstuvwxyz0123456789!\"§$%&/()=?"; + + // Generate a 128 bit random salt + // genrandom() returns cryptographically secure random data + uint8_t salt[SALT_LEN] = { 0 }; + if(getrandom(salt, sizeof(salt), 0) < 0) + { + printf("Could not generate random salt!\n"); + return EXIT_FAILURE; + } + + printf("Running time-performance test:\n"); + t_t_cost = 1; + t_s_cost = 1024; + size_t t_t_costs = 0, t_s_costs = 0; + double t_avg_sum = 0.0; + while(true) + { + const int ret = performance_test_task(t_s_cost, t_t_cost, password, sizeof(password), salt, &t_avg_sum, &t_t_costs, &t_s_costs); + + if(ret == -1) + return EXIT_FAILURE; + else if(ret == 1) + break; + + // Double time costs + t_t_cost *= 2; + } + + printf("\nRunning space-performance test:\n"); + s_t_cost = 256; + s_s_cost = 1; + size_t s_t_costs = 0, s_s_costs = 0; + double s_avg_sum = 0.0; + while(true) + { + const int ret = performance_test_task(s_s_cost, s_t_cost, password, sizeof(password), salt, &s_avg_sum, &s_t_costs, &s_s_costs); + + if(ret == -1) + return EXIT_FAILURE; + else if(ret == 1) + break; + + // Double space costs + s_s_cost *= 2; + } + + clock_gettime(CLOCK_MONOTONIC, &end); + const double elapsed = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1000000000.0; + + printf("\nTime-performance index: %8.1f it/s (s = %zu)\n", 1.0*t_s_costs/t_avg_sum, t_s_cost); + printf("Space-performance index: %8.1f it/s (t = %zu)\n", 1.0*s_s_costs/s_avg_sum, s_t_cost); + printf("\nTotal test time: %.1f seconds\n\n", elapsed); + + return EXIT_SUCCESS; +} diff --git a/src/config/password.h b/src/config/password.h index 6cacc758..d571b505 100644 --- a/src/config/password.h +++ b/src/config/password.h @@ -17,5 +17,6 @@ void sha256_raw_to_hex(uint8_t *data, char *buffer); char *create_password(const char *password) __attribute__((malloc)); bool verify_password(const char *password, const char *pwhash); +int run_performance_test(void); #endif //PASSWORD_H From de7227347b8e432bf9950f7c019ae2a481615e0f Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 23 Sep 2023 11:26:41 +0200 Subject: [PATCH 2/7] Run performance test during CI tests run Signed-off-by: DL6ER --- test/run.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/run.sh b/test/run.sh index 6f6454ed..e2563e82 100755 --- a/test/run.sh +++ b/test/run.sh @@ -130,6 +130,11 @@ kill "$(pidof pihole-FTL)" # Restore umask umask "$OLDUMASK" +# Run performance tests +if ! su pihole -s /bin/sh -c "/home/pihole/pihole-FTL --perf"; then + echo "pihole-FTL --perf failed to start" +fi + # Remove copied file rm /home/pihole/pihole-FTL From 2141db3d64841f7579ba789e4faff26e82b90657 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 23 Sep 2023 12:25:12 +0200 Subject: [PATCH 3/7] Add rate-limiting on password login attempts Signed-off-by: DL6ER --- src/api/auth.c | 12 +++++++- src/api/config.c | 2 +- src/api/docs/content/specs/auth.yaml | 8 ++++++ src/api/docs/content/specs/common.yaml | 20 +++++++++++++ src/config/cli.c | 2 +- src/config/password.c | 39 +++++++++++++++++++++----- src/config/password.h | 11 +++++++- test/api/libs/FTLAPI.py | 9 ++++-- test/api/test-rate-limit.py | 24 ++++++++++++++++ 9 files changed, 113 insertions(+), 14 deletions(-) create mode 100644 test/api/test-rate-limit.py diff --git a/src/api/auth.c b/src/api/auth.c index e0a4698b..1cec68b7 100644 --- a/src/api/auth.c +++ b/src/api/auth.c @@ -509,7 +509,8 @@ int api_auth(struct ftl_conn *api) // else: Login attempt // - Client tries to authenticate using a password, or // - There no password on this machine - if(empty_password ? true : verify_password(password, config.webserver.api.pwhash.v.s)) + const enum password_result result = empty_password ? true : verify_password(password, config.webserver.api.pwhash.v.s, true); + if(result == PASSWORD_CORRECT) { // Accepted @@ -604,6 +605,15 @@ int api_auth(struct ftl_conn *api) log_warn("No free API seats available, not authenticating client"); } } + else if(result == PASSWORD_RATE_LIMITED) + { + // Rate limited + log_debug(DEBUG_API, "API: Login attempt rate-limited"); + return send_json_error(api, 429, + "too_many_requests", + "Too many requests", + NULL); + } else { log_debug(DEBUG_API, "API: Password incorrect: '%s'", password); diff --git a/src/api/config.c b/src/api/config.c index cbc70aab..5f9e8b7a 100644 --- a/src/api/config.c +++ b/src/api/config.c @@ -287,7 +287,7 @@ static const char *getJSONvalue(struct conf_item *conf_item, cJSON *elem, struct char *pwhash = strlen(elem->valuestring) > 0 ? create_password(elem->valuestring) : strdup(""); // Verify that the password hash is valid - const bool verfied = verify_password(elem->valuestring, pwhash); + const bool verfied = verify_password(elem->valuestring, pwhash, false) == PASSWORD_CORRECT; if(!verfied) { diff --git a/src/api/docs/content/specs/auth.yaml b/src/api/docs/content/specs/auth.yaml index f46645fb..2c314181 100644 --- a/src/api/docs/content/specs/auth.yaml +++ b/src/api/docs/content/specs/auth.yaml @@ -79,6 +79,14 @@ components: allOf: - $ref: 'common.yaml#/components/errors/unauthorized' - $ref: 'common.yaml#/components/schemas/took' + '429': + description: Too Many Requests + content: + application/json: + schema: + allOf: + - $ref: 'common.yaml#/components/errors/too_many_requests' + - $ref: 'common.yaml#/components/schemas/took' delete: summary: Delete session tags: diff --git a/src/api/docs/content/specs/common.yaml b/src/api/docs/content/specs/common.yaml index 75129d23..fa82c83c 100644 --- a/src/api/docs/content/specs/common.yaml +++ b/src/api/docs/content/specs/common.yaml @@ -53,6 +53,26 @@ components: nullable: true description: "No additional data available" example: null + too_many_requests: + type: object + description: "Too many requests (rate limiting)" + properties: + error: + type: object + properties: + key: + type: string + description: "Machine-readable error type" + example: "too_many_requests" + message: + type: string + description: "Human-readable error message" + example: "Too many requests" + hint: + type: string + nullable: true + description: "No additional data available" + example: null headers: Location: description: Location of created resource diff --git a/src/config/cli.c b/src/config/cli.c index 756aabf5..15f4f19a 100644 --- a/src/config/cli.c +++ b/src/config/cli.c @@ -162,7 +162,7 @@ static bool readStringValue(struct conf_item *conf_item, const char *value, stru char *pwhash = strlen(value) > 0 ? create_password(value) : strdup(""); // Verify that the password hash is valid - const bool verfied = verify_password(value, pwhash); + const bool verfied = verify_password(value, pwhash, false) == PASSWORD_CORRECT; if(!verfied) { diff --git a/src/config/password.c b/src/config/password.c index edd6cc10..5b975abf 100644 --- a/src/config/password.c +++ b/src/config/password.c @@ -14,6 +14,8 @@ #include "password.h" // genrandom() with fallback #include "daemon.h" +// sleepms() +#include "timers.h" // Randomness generator #include "webserver/x509.h" @@ -305,16 +307,39 @@ char * __attribute__((malloc)) create_password(const char *password) return balloon_password(password, salt, true); } -bool verify_password(const char *password, const char* pwhash) +char verify_password(const char *password, const char* pwhash, const bool rate_limiting) { // No password supplied if(password == NULL || password[0] == '\0') - return false; + return PASSWORD_INCORRECT; // No password set if(pwhash == NULL || pwhash[0] == '\0') - return true; + return PASSWORD_CORRECT; + // Check if there has already been one login attempt within this second + static time_t last_password_attempt = 0; + static unsigned int num_password_attempts = 0; + if(rate_limiting && + last_password_attempt > 0 && + last_password_attempt == time(NULL)) + { + // Check if we have reached the maximum number of attempts + if(++num_password_attempts > MAX_PASSWORD_ATTEMPTS_PER_SECOND) + { + // Rate limit reached + sleepms(250); + return PASSWORD_RATE_LIMITED; + } + } + else + { + // Reset counter + num_password_attempts = 1; + last_password_attempt = time(NULL); + } + + // Check password hash format if(pwhash[0] == '$') { // Parse PHC string @@ -323,9 +348,9 @@ bool verify_password(const char *password, const char* pwhash) uint8_t *salt = NULL; uint8_t *config_hash = NULL; if(!parse_PHC_string(pwhash, &s_cost, &t_cost, &salt, &config_hash)) - return false; + return PASSWORD_INCORRECT; if(salt == NULL || config_hash == NULL) - return false; + return PASSWORD_INCORRECT; char *supplied = balloon_password(password, salt, false); const bool result = memcmp(config_hash, supplied, SHA256_DIGEST_SIZE) == 0; @@ -336,7 +361,7 @@ bool verify_password(const char *password, const char* pwhash) if(config_hash != NULL) free(config_hash); - return result; + return result ? PASSWORD_CORRECT : PASSWORD_INCORRECT; } else { @@ -361,7 +386,7 @@ bool verify_password(const char *password, const char* pwhash) } } - return result; + return result ? PASSWORD_CORRECT : PASSWORD_INCORRECT; } } diff --git a/src/config/password.h b/src/config/password.h index d571b505..e0a3a5f2 100644 --- a/src/config/password.h +++ b/src/config/password.h @@ -16,7 +16,16 @@ void sha256_raw_to_hex(uint8_t *data, char *buffer); char *create_password(const char *password) __attribute__((malloc)); -bool verify_password(const char *password, const char *pwhash); +char verify_password(const char *password, const char *pwhash, const bool rate_limiting); int run_performance_test(void); +enum password_result { + PASSWORD_INCORRECT = 0, + PASSWORD_CORRECT = 1, + PASSWORD_RATE_LIMITED = -1 +} __attribute__((packed)); + +// The maximum number of password attempts per second +#define MAX_PASSWORD_ATTEMPTS_PER_SECOND 3 + #endif //PASSWORD_H diff --git a/test/api/libs/FTLAPI.py b/test/api/libs/FTLAPI.py index 2b2519dc..a71bcec2 100644 --- a/test/api/libs/FTLAPI.py +++ b/test/api/libs/FTLAPI.py @@ -43,9 +43,10 @@ class FTLAPI(): self.verbose = False # Login to FTL API - self.login(password) - if self.session is None or 'valid' not in self.session or not self.session['valid']: - raise Exception("Could not login to FTL API") + if password is not None: + self.login(password) + if self.session is None or 'valid' not in self.session or not self.session['valid']: + raise Exception("Could not login to FTL API") def login(self, password: str = None): # Check if we even need to login @@ -65,6 +66,8 @@ class FTLAPI(): return response = self.POST("/api/auth", {"password": password}) + if "error" in response: + raise Exception("FTL returned error: " + json.dumps(response["error"])) if 'session' not in response: raise Exception("FTL returned invalid response item") self.session = response["session"] diff --git a/test/api/test-rate-limit.py b/test/api/test-rate-limit.py new file mode 100644 index 00000000..781a2d73 --- /dev/null +++ b/test/api/test-rate-limit.py @@ -0,0 +1,24 @@ +# Script that sends a number of randomly generated passwords to the +# /api/auth endpoint checking that rate limiting is enforced +import random +import string +from libs.FTLAPI import FTLAPI + +if __name__ == "__main__": + # Create FTLAPI object + ftl = FTLAPI("http://127.0.0.1:8080") + + # Try to login with random passwords + for i in range(0, 100): + pw = "".join(random.choices(string.printable, k=random.randint(1, 64))) + try: + ftl.login(pw) + except Exception as e: + if "too_many_requests" in str(e): + print("Rate-limited on attempt no. " + str(i)) + exit(0) + else: + print("Unexpected error: " + str(e)) + exit(1) + print("Rate-limiting was not enforced") + exit(1) From 465899575993bacf2bb6e10000216c69c908e31a Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 23 Sep 2023 13:55:20 +0200 Subject: [PATCH 4/7] Be more explicit in the variable definition (what is constant) Signed-off-by: DL6ER --- src/config/password.c | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/src/config/password.c b/src/config/password.c index 5b975abf..f56b2326 100644 --- a/src/config/password.c +++ b/src/config/password.c @@ -461,19 +461,6 @@ int run_performance_test(void) // Record starting time clock_gettime(CLOCK_MONOTONIC, &start); - // The space parameter s_cost determines how many blocks of working - // space the algorithm will require during its computation. It is - // common to set s_cost to a high value in order to increase the cost of - // hardware accelerators built by the adversary. - // The algorithm will need (s_cost + 1) * digest_size - size_t s_t_cost, s_s_cost; - - // The time parameter t_cost determines the number of rounds of - // computation that the algorithm will perform. This can be used to - // further increase the cost of computation without raising the memory - // requirement. - size_t t_t_cost, t_s_cost; - // Test password const uint8_t password[] = "abcdefghijklmnopqrstuvwxyz0123456789!\"§$%&/()=?"; @@ -487,8 +474,8 @@ int run_performance_test(void) } printf("Running time-performance test:\n"); - t_t_cost = 1; - t_s_cost = 1024; + size_t t_t_cost = 1; + const size_t t_s_cost = 1024; size_t t_t_costs = 0, t_s_costs = 0; double t_avg_sum = 0.0; while(true) @@ -505,8 +492,8 @@ int run_performance_test(void) } printf("\nRunning space-performance test:\n"); - s_t_cost = 256; - s_s_cost = 1; + const size_t s_t_cost = 256; + size_t s_s_cost = 1; size_t s_t_costs = 0, s_s_costs = 0; double s_avg_sum = 0.0; while(true) From a8839aa14eb5ff4be689a1d552b41e2ba57c0f37 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 30 Sep 2023 23:42:29 +0200 Subject: [PATCH 5/7] Improve the test by setting the T and S costs in the matrix to the same and computing a final average with a reliable error estimate (standard deviation = the square root of the variation of the performance index) Signed-off-by: DL6ER --- src/config/password.c | 98 +++++++++++++++++++++++++++++++++---------- 1 file changed, 75 insertions(+), 23 deletions(-) diff --git a/src/config/password.c b/src/config/password.c index f56b2326..682f8fed 100644 --- a/src/config/password.c +++ b/src/config/password.c @@ -399,7 +399,9 @@ static double sqroot(double square) return root; } -static int performance_test_task(const size_t s_cost, const size_t t_cost, const uint8_t password[], const size_t pwlen, uint8_t salt[SALT_LEN], double *avg_sum, size_t *t_costs, size_t *s_costs) +static int performance_test_task(const size_t s_cost, const size_t t_cost, const uint8_t password[], + const size_t pwlen, uint8_t salt[SALT_LEN], const size_t rel, + double *elapsed1, double *elapsed2) { struct timespec start, end, end2; // Scratch buffer scratch is a user allocated working space required by @@ -436,20 +438,18 @@ static int performance_test_task(const size_t s_cost, const size_t t_cost, const free(scratch); // Compute elapsed time - const double elapsed = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1000000000.0; - const double elapsed2 = (end2.tv_sec - end.tv_sec) + (end2.tv_nsec - end.tv_nsec) / 1000000000.0; + *elapsed1 = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1000000000.0; + *elapsed2 = (end2.tv_sec - end.tv_sec) + (end2.tv_nsec - end.tv_nsec) / 1000000000.0; char prefix[2] = { 0 }; double formatted = 0.0; format_memory_size(prefix, (unsigned long long)scratch_size, &formatted); - const double avg = (elapsed + elapsed2)/2; - *avg_sum += avg; - *t_costs += t_cost; - *s_costs += s_cost; - const double stdev = sqroot(((elapsed - avg)*(elapsed - avg) + (elapsed2 - avg)*(elapsed2 - avg))/2); - printf("Balloon with s = %zu, t = %zu took %.1f +/- %.1f milliseconds (scratch buffer %.1f%sB)\n", s_cost, t_cost, 1e3*avg, 1e3*stdev, formatted, prefix); + const double avg = (*elapsed1 + *elapsed2)/2; + const double stdev = sqroot(((*elapsed1 - avg)*(*elapsed1 - avg) + (*elapsed2 - avg)*(*elapsed2 - avg))/2); + printf("s = %zu, t = %zu took %.1f +/- %.1f ms (scratch buffer %.1f%sB) -> %.1f\n", + s_cost, t_cost, 1e3*avg, 1e3*stdev, formatted, prefix, 1.0*rel/avg); // Break if test took longer than two seconds - if(elapsed > 2) + if(avg > 2) return 1; return 0; } @@ -474,31 +474,47 @@ int run_performance_test(void) } printf("Running time-performance test:\n"); - size_t t_t_cost = 1; - const size_t t_s_cost = 1024; - size_t t_t_costs = 0, t_s_costs = 0; - double t_avg_sum = 0.0; + size_t t_t_cost = 16; + const size_t t_s_cost = 512; + cJSON *time_test = cJSON_CreateArray(); + unsigned int i = 0; while(true) { - const int ret = performance_test_task(t_s_cost, t_t_cost, password, sizeof(password), salt, &t_avg_sum, &t_t_costs, &t_s_costs); + double elapsed1 = 0.0, elapsed2 = 0.0; + const int ret = performance_test_task(t_s_cost, t_t_cost, password, sizeof(password), salt, + t_t_cost, &elapsed1, &elapsed2); if(ret == -1) return EXIT_FAILURE; - else if(ret == 1) + + if(i > 0) + { + // We do not want to include the first test in the + // average as the first call is slower + cJSON_AddItemToArray(time_test, cJSON_CreateNumber(1.0*t_t_cost/elapsed1)); + cJSON_AddItemToArray(time_test, cJSON_CreateNumber(1.0*t_t_cost/elapsed2)); + } + + if(ret == 1) break; // Double time costs t_t_cost *= 2; + i++; } printf("\nRunning space-performance test:\n"); - const size_t s_t_cost = 256; - size_t s_s_cost = 1; - size_t s_t_costs = 0, s_s_costs = 0; - double s_avg_sum = 0.0; + const size_t s_t_cost = 512; + size_t s_s_cost = 8; + cJSON *space_test = cJSON_CreateArray(); while(true) { - const int ret = performance_test_task(s_s_cost, s_t_cost, password, sizeof(password), salt, &s_avg_sum, &s_t_costs, &s_s_costs); + double elapsed1 = 0.0, elapsed2 = 0.0; + const int ret = performance_test_task(s_s_cost, s_t_cost, password, sizeof(password), salt, + s_s_cost, &elapsed1, &elapsed2); + + cJSON_AddItemToArray(space_test, cJSON_CreateNumber(1.0*s_s_cost/elapsed1)); + cJSON_AddItemToArray(space_test, cJSON_CreateNumber(1.0*s_s_cost/elapsed2)); if(ret == -1) return EXIT_FAILURE; @@ -512,8 +528,44 @@ int run_performance_test(void) clock_gettime(CLOCK_MONOTONIC, &end); const double elapsed = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1000000000.0; - printf("\nTime-performance index: %8.1f it/s (s = %zu)\n", 1.0*t_s_costs/t_avg_sum, t_s_cost); - printf("Space-performance index: %8.1f it/s (t = %zu)\n", 1.0*s_s_costs/s_avg_sum, s_t_cost); + // Compute average time and space costs from data in cJSON arrays + cJSON *item = NULL; + double t_avg_sum1 = 0.0; + cJSON_ArrayForEach(item, time_test) + { + t_avg_sum1 += item->valuedouble; + } + t_avg_sum1 /= cJSON_GetArraySize(time_test); + + double s_avg_sum1 = 0.0; + cJSON_ArrayForEach(item, space_test) + { + s_avg_sum1 += item->valuedouble; + } + s_avg_sum1 /= cJSON_GetArraySize(space_test); + + // Get standard deviations + double t_stdev_sum1 = 0.0; + cJSON_ArrayForEach(item, time_test) + { + t_stdev_sum1 += (item->valuedouble - t_avg_sum1)*(item->valuedouble - t_avg_sum1); + } + t_stdev_sum1 = sqroot(t_stdev_sum1/cJSON_GetArraySize(time_test)); + + double s_stdev_sum1 = 0.0; + cJSON_ArrayForEach(item, space_test) + { + s_stdev_sum1 += (item->valuedouble - s_avg_sum1)*(item->valuedouble - s_avg_sum1); + } + s_stdev_sum1 = sqroot(s_stdev_sum1/cJSON_GetArraySize(space_test)); + + // Free allocated memory + cJSON_Delete(time_test); + cJSON_Delete(space_test); + + // Print results + printf("\nAverage time-performance index: %8.1f +/- %.1f (s = %zu)\n", t_avg_sum1, t_stdev_sum1, t_s_cost); + printf("Average space-performance index: %8.1f +/- %.1f (t = %zu)\n", s_avg_sum1, s_stdev_sum1, s_t_cost); printf("\nTotal test time: %.1f seconds\n\n", elapsed); return EXIT_SUCCESS; From b21475fcb48649d3214a10307411ef42cfe22e2e Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sun, 1 Oct 2023 00:05:19 +0200 Subject: [PATCH 6/7] Better scale performance index Signed-off-by: DL6ER --- src/config/password.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/config/password.c b/src/config/password.c index 682f8fed..3fb676f3 100644 --- a/src/config/password.c +++ b/src/config/password.c @@ -400,7 +400,7 @@ static double sqroot(double square) } static int performance_test_task(const size_t s_cost, const size_t t_cost, const uint8_t password[], - const size_t pwlen, uint8_t salt[SALT_LEN], const size_t rel, + const size_t pwlen, uint8_t salt[SALT_LEN], double *elapsed1, double *elapsed2) { struct timespec start, end, end2; @@ -445,8 +445,8 @@ static int performance_test_task(const size_t s_cost, const size_t t_cost, const format_memory_size(prefix, (unsigned long long)scratch_size, &formatted); const double avg = (*elapsed1 + *elapsed2)/2; const double stdev = sqroot(((*elapsed1 - avg)*(*elapsed1 - avg) + (*elapsed2 - avg)*(*elapsed2 - avg))/2); - printf("s = %zu, t = %zu took %.1f +/- %.1f ms (scratch buffer %.1f%sB) -> %.1f\n", - s_cost, t_cost, 1e3*avg, 1e3*stdev, formatted, prefix, 1.0*rel/avg); + printf("s = %5zu, t = %5zu took %6.1f +/- %4.1f ms (scratch buffer %6.1f%1sB) -> %.0f\n", + s_cost, t_cost, 1e3*avg, 1e3*stdev, formatted, prefix, 1.0*(s_cost*t_cost)/avg); // Break if test took longer than two seconds if(avg > 2) @@ -482,7 +482,7 @@ int run_performance_test(void) { double elapsed1 = 0.0, elapsed2 = 0.0; const int ret = performance_test_task(t_s_cost, t_t_cost, password, sizeof(password), salt, - t_t_cost, &elapsed1, &elapsed2); + &elapsed1, &elapsed2); if(ret == -1) return EXIT_FAILURE; @@ -491,8 +491,8 @@ int run_performance_test(void) { // We do not want to include the first test in the // average as the first call is slower - cJSON_AddItemToArray(time_test, cJSON_CreateNumber(1.0*t_t_cost/elapsed1)); - cJSON_AddItemToArray(time_test, cJSON_CreateNumber(1.0*t_t_cost/elapsed2)); + cJSON_AddItemToArray(time_test, cJSON_CreateNumber(1.0*(t_s_cost*t_t_cost)/elapsed1)); + cJSON_AddItemToArray(time_test, cJSON_CreateNumber(1.0*(t_s_cost*t_t_cost)/elapsed2)); } if(ret == 1) @@ -511,10 +511,10 @@ int run_performance_test(void) { double elapsed1 = 0.0, elapsed2 = 0.0; const int ret = performance_test_task(s_s_cost, s_t_cost, password, sizeof(password), salt, - s_s_cost, &elapsed1, &elapsed2); + &elapsed1, &elapsed2); - cJSON_AddItemToArray(space_test, cJSON_CreateNumber(1.0*s_s_cost/elapsed1)); - cJSON_AddItemToArray(space_test, cJSON_CreateNumber(1.0*s_s_cost/elapsed2)); + cJSON_AddItemToArray(space_test, cJSON_CreateNumber(1.0*(s_t_cost*s_s_cost)/elapsed1)); + cJSON_AddItemToArray(space_test, cJSON_CreateNumber(1.0*(s_t_cost*s_s_cost)/elapsed2)); if(ret == -1) return EXIT_FAILURE; @@ -564,8 +564,8 @@ int run_performance_test(void) cJSON_Delete(space_test); // Print results - printf("\nAverage time-performance index: %8.1f +/- %.1f (s = %zu)\n", t_avg_sum1, t_stdev_sum1, t_s_cost); - printf("Average space-performance index: %8.1f +/- %.1f (t = %zu)\n", s_avg_sum1, s_stdev_sum1, s_t_cost); + printf("\nAverage time-performance index: %9.0f +/- %.0f (s = %zu)\n", t_avg_sum1, t_stdev_sum1, t_s_cost); + printf("Average space-performance index: %9.0f +/- %.0f (t = %zu)\n", s_avg_sum1, s_stdev_sum1, s_t_cost); printf("\nTotal test time: %.1f seconds\n\n", elapsed); return EXIT_SUCCESS; From 856aae1bef001dd0eeee035e68a661a8cc44cf1d Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sun, 8 Oct 2023 07:40:19 +0200 Subject: [PATCH 7/7] Add hint to login rate-limiting logging. We also remove the debug logging as there will always be a WARN Signed-off-by: DL6ER --- src/api/auth.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/api/auth.c b/src/api/auth.c index 1cec68b7..aa8fb9af 100644 --- a/src/api/auth.c +++ b/src/api/auth.c @@ -608,11 +608,10 @@ int api_auth(struct ftl_conn *api) else if(result == PASSWORD_RATE_LIMITED) { // Rate limited - log_debug(DEBUG_API, "API: Login attempt rate-limited"); return send_json_error(api, 429, "too_many_requests", "Too many requests", - NULL); + "login rate limiting"); } else {