summaryrefslogtreecommitdiff
path: root/src/ipcpd/unicast/ca.c
blob: d0ee2f735e946586643106e772605269e7263baf (plain)
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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
/*
 * Ouroboros - Copyright (C) 2016 - 2026
 *
 * Congestion Avoidance
 *
 *    Dimitri Staessens <dimitri@ouroboros.rocks>
 *    Sander Vrijders   <sander@ouroboros.rocks>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., http://www.fsf.org/about/contact/.
 */

#define OUROBOROS_PREFIX "ca"

#include "config.h"

#include <ouroboros/list.h>
#include <ouroboros/logs.h>

#include "ca.h"
#include "ca/pol.h"

#include <pthread.h>
#include <stdlib.h>

/*
 * A ca_ctx holds congestion state for a (peer address, qos cube) PATH,
 * not for a flow. In the default build the façade interns one ctx per
 * (addr, qc) and shares it across every flow on that path; the policy
 * runs on the shared ctx and cannot tell one flow from many. Per-flow
 * ctx (IPCP_CA_PER_FLOW) is a testing build only: it skips interning so
 * every flow gets its own ctx.
 */

struct ca_ctx {
        uint64_t         addr;
        qoscube_t        qc;
        size_t           refs;
        void *           pol;   /* policy ctx (ops->ctx_create result) */
        struct list_head next;
};

struct {
        struct ca_ops *  ops;
#ifndef IPCP_CA_PER_FLOW
        struct list_head buckets[CA_BUCKETS];
        pthread_mutex_t  mtx;
#endif
} ca;

int ca_init(enum pol_cong_avoid pol)
{
#ifndef IPCP_CA_PER_FLOW
        size_t i;
#endif
        switch(pol) {
        case CA_NONE:
                log_dbg("Disabling congestion control.");
                ca.ops = &nop_ca_ops;
                break;
        case CA_MB_ECN:
                log_dbg("Using multi-bit ECN.");
                ca.ops = &mb_ecn_ca_ops;
                break;
        default:
                return -1;
        }

#ifndef IPCP_CA_PER_FLOW
        for (i = 0; i < CA_BUCKETS; i++)
                list_head_init(&ca.buckets[i]);

        if (pthread_mutex_init(&ca.mtx, NULL) != 0)
                return -1;
#endif
        return 0;
}

void ca_fini(void)
{
#ifndef IPCP_CA_PER_FLOW
        size_t i;

        /* Data path is stopped; drain any ctx a flow left interned. */
        for (i = 0; i < CA_BUCKETS; i++) {
                struct list_head * p;
                struct list_head * h;

                list_for_each_safe(p, h, &ca.buckets[i]) {
                        struct ca_ctx * ctx;
                        ctx = list_entry(p, struct ca_ctx, next);
                        list_del(&ctx->next);
                        ca.ops->ctx_destroy(ctx->pol);
                        free(ctx);
                }
        }

        pthread_mutex_destroy(&ca.mtx);
#endif
        ca.ops = NULL;
}

#ifndef IPCP_CA_PER_FLOW
static size_t ca_bucket(uint64_t addr,
                        qoscube_t qc)
{
        return (addr ^ (addr >> 32) ^ (uint64_t) qc) & (CA_BUCKETS - 1);
}
#endif

void * ca_ctx_get(uint64_t  addr,
                  qoscube_t qc)
{
        struct ca_ctx *    ctx;
#ifndef IPCP_CA_PER_FLOW
        struct list_head * p;
        size_t             b = ca_bucket(addr, qc);

        pthread_mutex_lock(&ca.mtx);

        list_for_each(p, &ca.buckets[b]) {
                ctx = list_entry(p, struct ca_ctx, next);
                if (ctx->addr == addr && ctx->qc == qc) {
                        ctx->refs++;
                        pthread_mutex_unlock(&ca.mtx);
                        return ctx;
                }
        }
#endif
        ctx = malloc(sizeof(*ctx));
        if (ctx == NULL)
                goto fail_ctx;

        ctx->pol = ca.ops->ctx_create();
        if (ctx->pol == NULL)
                goto fail_pol;

        ctx->addr = addr;
        ctx->qc   = qc;
        ctx->refs = 1;

#ifndef IPCP_CA_PER_FLOW
        list_add(&ctx->next, &ca.buckets[b]);

        pthread_mutex_unlock(&ca.mtx);
#endif
        return ctx;
 fail_pol:
        free(ctx);
 fail_ctx:
#ifndef IPCP_CA_PER_FLOW
        pthread_mutex_unlock(&ca.mtx);
#endif
        return NULL;
}

void ca_ctx_put(void * _ctx)
{
        struct ca_ctx * ctx = _ctx;

#ifndef IPCP_CA_PER_FLOW
        pthread_mutex_lock(&ca.mtx);

        if (--ctx->refs > 0) {
                pthread_mutex_unlock(&ca.mtx);
                return;
        }

        list_del(&ctx->next);

        pthread_mutex_unlock(&ca.mtx);
#endif
        ca.ops->ctx_destroy(ctx->pol);

        free(ctx);
}

time_t ca_ctx_update_snd(void *     _ctx,
                         size_t     len,
                         uint8_t    lecn,
                         uint64_t * ftag)
{
        struct ca_ctx * ctx = _ctx;

        return ca.ops->ctx_update_snd(ctx->pol, len, lecn, ftag);
}

bool ca_ctx_update_rcv(void *     _ctx,
                       size_t     len,
                       uint8_t    ecn,
                       uint8_t    cap,
                       uint16_t * ece,
                       uint8_t *  fcap)
{
        struct ca_ctx * ctx = _ctx;

        return ca.ops->ctx_update_rcv(ctx->pol, len, ecn, cap, ece, fcap);
}

void ca_ctx_update_ece(void *   _ctx,
                       uint16_t ece,
                       uint8_t  cap)
{
        struct ca_ctx * ctx = _ctx;

        return ca.ops->ctx_update_ece(ctx->pol, ece, cap);
}

int  ca_calc_ecn(size_t    queued,
                 uint8_t * ecn,
                 qoscube_t qc,
                 size_t    len)
{
        return ca.ops->calc_ecn(queued, ecn, qc, len);
}

bool ca_marks_ecn(void)
{
        return ca.ops->marks_ecn;
}

ssize_t ca_print_stats(void * _ctx,
                       char * buf,
                       size_t len)
{
        struct ca_ctx * ctx = _ctx;

        if (ca.ops->print_stats == NULL)
                return 0;

        return ca.ops->print_stats(ctx->pol, buf, len);
}