/* gprs_codel.cpp * * Copyright (C) 2015 by Sysmocom s.f.m.c. GmbH * Author: Jacob Erlbeck * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "gprs_codel.h" #include "gprs_debug.h" #include #include #include #include static void control_law(struct gprs_codel *state, struct timeval *delta) { /* 256 / sqrt(x), limited to 255 */ static uint8_t inv_sqrt_tab[] = {255, 255, 181, 147, 128, 114, 104, 96, 90, 85, 80, 77, 73, 71, 68, 66, 64, 62, 60, 58, 57, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 45, 44, 43, 43, 42, 42, 41, 40, 40, 39, 39, 39, 38, 38, 37, 37, 36, 36, 36, 35, 35, 35, 34, 34, 34, 33, 33, 33, 33, 32, 32, 32, 32, 31, 31, 31, 31, 30, 30, 30, 30, 29, 29, 29, 29, 29, 28, 28, 28, 28, 28, 28, 27, 27, 27, 27, 27, 27, 26, 26, 26, 26, 26, 26, 26, 25, 25, 25, 25, 25, 25, 25, 25, 24, 24, 24, 24, 24, 24, 24, 24, 24, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17 }; uint_fast32_t delta_usecs; uint_fast32_t inv_sqrt; div_t q; if (state->count >= ARRAY_SIZE(inv_sqrt_tab)) inv_sqrt = 16; else inv_sqrt = inv_sqrt_tab[state->count]; /* delta = state->interval / sqrt(count) */ delta_usecs = state->interval.tv_sec * 1000000 + state->interval.tv_usec; delta_usecs = delta_usecs * inv_sqrt / 256; q = div(delta_usecs, 1000000); delta->tv_sec = q.quot; delta->tv_usec = q.rem; } void gprs_codel_init(struct gprs_codel *state) { static const struct gprs_codel init_state = {0}; *state = init_state; gprs_codel_set_interval(state, -1); gprs_codel_set_maxpacket(state, -1); } void gprs_codel_set_interval(struct gprs_codel *state, int interval_ms) { div_t q; if (interval_ms <= 0) interval_ms = GPRS_CODEL_DEFAULT_INTERVAL_MS; q = div(interval_ms, 1000); state->interval.tv_sec = q.quot; state->interval.tv_usec = q.rem * 1000; /* target ~ 5% of interval */ q = div(interval_ms * 13 / 256, 1000); state->target.tv_sec = q.quot; state->target.tv_usec = q.rem * 1000; } void gprs_codel_set_maxpacket(struct gprs_codel *state, int maxpacket) { if (maxpacket < 0) maxpacket = GPRS_CODEL_DEFAULT_MAXPACKET; state->maxpacket = maxpacket; } /* * This is an broken up variant of the algorithm being described in * http://queue.acm.org/appendices/codel.html */ int gprs_codel_control(struct gprs_codel *state, const struct timeval *recv, const struct timeval *now, int bytes) { struct timeval sojourn_time; struct timeval delta; if (recv == NULL) goto stop_dropping; timersub(now, recv, &sojourn_time); if (timercmp(&sojourn_time, &state->target, <)) goto stop_dropping; if (bytes >= 0 && (unsigned)bytes <= state->maxpacket) goto stop_dropping; if (!timerisset(&state->first_above_time)) { timeradd(now, &state->interval, &state->first_above_time); goto not_ok_to_drop; } if (timercmp(now, &state->first_above_time, <)) goto not_ok_to_drop; /* Ok to drop */ if (!state->dropping) { int recently = 0; int in_drop_cycle = 0; if (timerisset(&state->drop_next)) { timersub(now, &state->drop_next, &delta); in_drop_cycle = timercmp(&delta, &state->interval, <); recently = in_drop_cycle; } if (!recently) { timersub(now, &state->first_above_time, &delta); recently = !timercmp(&delta, &state->interval, <); }; if (!recently) return 0; state->dropping = 1; if (in_drop_cycle && state->count > 2) state->count -= 2; else state->count = 1; state->drop_next = *now; } else { if (timercmp(now, &state->drop_next, <)) return 0; state->count += 1; } control_law(state, &delta); timeradd(&state->drop_next, &delta, &state->drop_next); #if 1 LOGP(DRLCMAC, LOGL_INFO, "CoDel decided to drop packet, window = %d.%03dms, count = %d\n", (int)delta.tv_sec, (int)(delta.tv_usec / 1000), state->count); #endif return 1; stop_dropping: timerclear(&state->first_above_time); not_ok_to_drop: state->dropping = 0; return 0; }