summaryrefslogtreecommitdiff
path: root/src/ratelimit.c
blob: c6f0e01d60e2c29c9bc3e8cc2df8257f6725d4e7 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#define TABLE_SIZE    2048
#define MAX_KEY_LEN   46
#define STALE_SECONDS 30

struct rl_entry {
    char   ip[ MAX_KEY_LEN ];
    time_t last_request;
    int    count;
    int    in_use;
};

struct rl_table {
    struct rl_entry entries[ TABLE_SIZE ];
    pthread_mutex_t mutex;
};

struct rl_table* global_ratelimit = NULL;

void ratelimit_init(void) {
    if (global_ratelimit != NULL) return;

    struct rl_table* table = malloc(sizeof(struct rl_table));
    if (!table) {
        perror("failed to alloc ratelimit table");
        exit(1);
    }
    memset(table->entries, 0, sizeof(table->entries));
    pthread_mutex_init(&table->mutex, NULL);

    global_ratelimit = table;
}

static uint32_t hash_ip(const char* ip) {
    uint32_t hash = 5381;
    while (*ip) hash = (hash << 5) + hash + *ip++;
    return hash % TABLE_SIZE;
}

int ratelimit_check(const char* ip) {
    ratelimit_init();
    pthread_mutex_lock(&global_ratelimit->mutex);

    time_t   now    = time(NULL);
    uint32_t index  = hash_ip(ip);
    uint32_t probes = 0;

    while (probes < TABLE_SIZE) {
        struct rl_entry* entry = &global_ratelimit->entries[ index ];

        if (!entry->in_use) {
            strncpy(entry->ip, ip, MAX_KEY_LEN - 1);
            entry->ip[ MAX_KEY_LEN - 1 ] = '\0';
            entry->last_request          = now;
            entry->count                 = 1;
            entry->in_use                = 1;
            pthread_mutex_unlock(&global_ratelimit->mutex);
            return 1;
        }

        if (strcmp(entry->ip, ip) == 0) {
            if (now > entry->last_request) {
                entry->last_request = now;
                entry->count        = 1;
            } else if (entry->count >= 4) {
                pthread_mutex_unlock(&global_ratelimit->mutex);
                return 0;
            } else {
                entry->count++;
            }
            pthread_mutex_unlock(&global_ratelimit->mutex);
            return 1;
        }

        if (entry->in_use && (now - entry->last_request) > STALE_SECONDS) {
            entry->in_use       = 0;
            entry->count        = 0;
            entry->last_request = 0;
            entry->ip[ 0 ]      = '\0';
            strncpy(entry->ip, ip, MAX_KEY_LEN - 1);
            entry->ip[ MAX_KEY_LEN - 1 ] = '\0';
            entry->last_request          = now;
            entry->count                 = 1;
            entry->in_use                = 1;
            pthread_mutex_unlock(&global_ratelimit->mutex);
            return 1;
        }

        index = (index + 1) % TABLE_SIZE;
        probes++;
    }

    pthread_mutex_unlock(&global_ratelimit->mutex);
    return 0;
}

void ratelimit_free(struct rl_table* table) {
    if (table) {
        pthread_mutex_destroy(&table->mutex);
        free(table);
    }
}