1 /* $NetBSD: ratelimit.c,v 1.3 2025/09/19 05:07:38 mrg Exp $ */ 2 3 /*- 4 * Copyright (c) 2021 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by James Browning, Gabe Coffland, Alex Gavin, and Solomon Ritzow. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 * POSSIBILITY OF SUCH DAMAGE. 30 */ 31 #include <sys/cdefs.h> 32 __RCSID("$NetBSD: ratelimit.c,v 1.3 2025/09/19 05:07:38 mrg Exp $"); 33 34 #include <sys/param.h> 35 #include <sys/queue.h> 36 37 #include <arpa/inet.h> 38 39 #include <stdio.h> 40 #include <stdlib.h> 41 #include <syslog.h> 42 #include <unistd.h> 43 #include <string.h> 44 #include <errno.h> 45 #include <stddef.h> 46 47 #include "inetd.h" 48 49 union addr { 50 struct in_addr ipv4_addr; 51 /* ensure aligned for comparison in rl_ipv6_eq (already is on 64-bit) */ 52 #ifdef INET6 53 struct in6_addr ipv6_addr __attribute__((aligned(16))); 54 #endif 55 char other_addr[NI_MAXHOST]; 56 }; 57 58 static void rl_reset(struct servtab *, time_t); 59 static time_t rl_time(void); 60 static void rl_get_name(struct servtab *, int, union addr *); 61 static void rl_drop_connection(struct servtab *, int); 62 static struct rl_ip_node *rl_add(struct servtab *, union addr *); 63 static struct rl_ip_node *rl_try_get_ip(struct servtab *, union addr *); 64 static bool rl_ip_eq(struct servtab *, union addr *, struct rl_ip_node *); 65 #ifdef INET6 66 static bool rl_ipv6_eq(struct in6_addr *, struct in6_addr *); 67 #endif 68 #ifdef DEBUG_ENABLE 69 static void rl_print_found_node(struct servtab *, struct rl_ip_node *); 70 #endif 71 static void rl_log_address_exceed(struct servtab *, struct rl_ip_node *); 72 static const char *rl_node_tostring(struct servtab *, struct rl_ip_node *, char[NI_MAXHOST]); 73 static bool rl_process_service_max(struct servtab *, int, time_t *); 74 static bool rl_process_ip_max(struct servtab *, int, time_t *); 75 76 /* Return 0 on allow, -1 if connection should be blocked */ 77 int 78 rl_process(struct servtab *sep, int ctrl) 79 { 80 time_t now = -1; 81 82 DPRINTF(SERV_FMT ": processing rate-limiting", 83 SERV_PARAMS(sep)); 84 DPRINTF(SERV_FMT ": se_service_max " 85 "%zu and se_count %zu", SERV_PARAMS(sep), 86 sep->se_service_max, sep->se_count); 87 88 if (sep->se_count == 0) { 89 now = rl_time(); 90 sep->se_time = now; 91 } 92 93 if (!rl_process_service_max(sep, ctrl, &now) 94 || !rl_process_ip_max(sep, ctrl, &now)) { 95 return -1; 96 } 97 98 DPRINTF(SERV_FMT ": running service ", SERV_PARAMS(sep)); 99 100 /* se_count is only incremented if rl_process will return 0 */ 101 sep->se_count++; 102 return 0; 103 } 104 105 /* 106 * Get the identifier for the remote peer based on sep->se_socktype and 107 * sep->se_family 108 */ 109 static void 110 rl_get_name(struct servtab *sep, int ctrl, union addr *out) 111 { 112 union { 113 struct sockaddr_storage ss; 114 struct sockaddr sa; 115 struct sockaddr_in sin; 116 struct sockaddr_in6 sin6; 117 } addr; 118 119 /* Get the sockaddr of socket ctrl */ 120 switch (sep->se_socktype) { 121 case SOCK_STREAM: { 122 socklen_t len = sizeof(struct sockaddr_storage); 123 if (getpeername(ctrl, &addr.sa, &len) == -1) { 124 /* error, log it and skip ip rate limiting */ 125 syslog(LOG_ERR, 126 SERV_FMT " failed to get peer name of the " 127 "connection", SERV_PARAMS(sep)); 128 exit(EXIT_FAILURE); 129 } 130 break; 131 } 132 case SOCK_DGRAM: { 133 struct msghdr header = { 134 .msg_name = &addr.sa, 135 .msg_namelen = sizeof(struct sockaddr_storage), 136 /* scatter/gather and control info is null */ 137 }; 138 ssize_t count; 139 140 /* Peek so service can still get the packet */ 141 count = recvmsg(ctrl, &header, MSG_PEEK); 142 if (count == -1) { 143 syslog(LOG_ERR, 144 "failed to get dgram source address: %s; exiting", 145 strerror(errno)); 146 exit(EXIT_FAILURE); 147 } 148 break; 149 } 150 default: 151 DPRINTF(SERV_FMT ": ip_max rate limiting not supported for " 152 "socktype", SERV_PARAMS(sep)); 153 syslog(LOG_ERR, SERV_FMT 154 ": ip_max rate limiting not supported for socktype", 155 SERV_PARAMS(sep)); 156 exit(EXIT_FAILURE); 157 } 158 159 /* Convert addr to to rate limiting address */ 160 switch (sep->se_family) { 161 case AF_INET: 162 out->ipv4_addr = addr.sin.sin_addr; 163 break; 164 #ifdef INET6 165 case AF_INET6: 166 out->ipv6_addr = addr.sin6.sin6_addr; 167 break; 168 #endif 169 default: { 170 int res = getnameinfo(&addr.sa, 171 (socklen_t)addr.sa.sa_len, 172 out->other_addr, NI_MAXHOST, 173 NULL, 0, 174 NI_NUMERICHOST 175 ); 176 if (res != 0) { 177 syslog(LOG_ERR, 178 SERV_FMT ": failed to get name info of " 179 "the incoming connection: %s; exiting", 180 SERV_PARAMS(sep), gai_strerror(res)); 181 exit(EXIT_FAILURE); 182 } 183 break; 184 } 185 } 186 } 187 188 static void 189 rl_drop_connection(struct servtab *sep, int ctrl) 190 { 191 192 if (sep->se_wait == 0 && sep->se_socktype == SOCK_STREAM) { 193 /* 194 * If the fd isn't a listen socket, 195 * close the individual connection too. 196 */ 197 close(ctrl); 198 return; 199 } 200 if (sep->se_socktype != SOCK_DGRAM) { 201 return; 202 } 203 /* 204 * Drop the single datagram the service would have 205 * consumed if nowait. If this is a wait service, this 206 * will consume 1 datagram, and further received packets 207 * will be removed in the same way. 208 */ 209 struct msghdr header = { 210 /* All fields null, just consume one message */ 211 }; 212 ssize_t count; 213 214 count = recvmsg(ctrl, &header, 0); 215 if (count == -1) { 216 syslog(LOG_ERR, 217 SERV_FMT ": failed to consume nowait dgram: %s", 218 SERV_PARAMS(sep), strerror(errno)); 219 exit(EXIT_FAILURE); 220 } 221 DPRINTF(SERV_FMT ": dropped dgram message", 222 SERV_PARAMS(sep)); 223 } 224 225 static time_t 226 rl_time(void) 227 { 228 struct timespec ts; 229 if (clock_gettime(CLOCK_MONOTONIC, &ts) == -1) { 230 syslog(LOG_ERR, "clock_gettime for rate limiting failed: %s; " 231 "exiting", strerror(errno)); 232 /* Exit inetd if rate limiting fails */ 233 exit(EXIT_FAILURE); 234 } 235 return ts.tv_sec; 236 } 237 238 /* Add addr to IP tracking or return NULL if malloc fails */ 239 static struct rl_ip_node * 240 rl_add(struct servtab *sep, union addr *addr) 241 { 242 243 struct rl_ip_node *node; 244 size_t node_size, bufsize; 245 #ifdef DEBUG_ENABLE 246 char buffer[NI_MAXHOST]; 247 #endif 248 249 switch(sep->se_family) { 250 case AF_INET: 251 /* ip_node to end of IPv4 address */ 252 node_size = offsetof(struct rl_ip_node, ipv4_addr) 253 + sizeof(struct in_addr); 254 break; 255 case AF_INET6: 256 /* ip_node to end of IPv6 address */ 257 node_size = offsetof(struct rl_ip_node, ipv6_addr) 258 + sizeof(struct in6_addr); 259 break; 260 default: 261 /* ip_node to other_addr plus size of string + NULL */ 262 bufsize = strlen(addr->other_addr) + sizeof(char); 263 node_size = offsetof(struct rl_ip_node, other_addr) + bufsize; 264 break; 265 } 266 267 node_size = MAX(node_size, sizeof *node); 268 269 node = malloc(node_size); 270 if (node == NULL) { 271 if (errno == ENOMEM) { 272 return NULL; 273 } else { 274 syslog(LOG_ERR, "malloc failed unexpectedly: %s", 275 strerror(errno)); 276 exit(EXIT_FAILURE); 277 } 278 } 279 280 node->count = 0; 281 282 /* copy the data into the new allocation */ 283 switch(sep->se_family) { 284 case AF_INET: 285 node->ipv4_addr = addr->ipv4_addr; 286 break; 287 case AF_INET6: 288 /* Hopefully this is inlined, means the same thing as memcpy */ 289 __builtin_memcpy(&node->ipv6_addr, &addr->ipv6_addr, 290 sizeof(struct in6_addr)); 291 break; 292 default: 293 strlcpy(node->other_addr, addr->other_addr, bufsize); 294 break; 295 } 296 297 /* initializes 'entries' member to NULL automatically */ 298 SLIST_INSERT_HEAD(&sep->se_rl_ip_list, node, entries); 299 300 DPRINTF(SERV_FMT ": add '%s' to rate limit tracking (%zu byte record)", 301 SERV_PARAMS(sep), rl_node_tostring(sep, node, buffer), node_size); 302 303 return node; 304 } 305 306 static void 307 rl_reset(struct servtab *sep, time_t now) 308 { 309 DPRINTF(SERV_FMT ": %ji seconds passed; resetting rate limiting ", 310 SERV_PARAMS(sep), (intmax_t)(now - sep->se_time)); 311 312 sep->se_count = 0; 313 sep->se_time = now; 314 if (sep->se_ip_max != SERVTAB_UNSPEC_SIZE_T) { 315 rl_clear_ip_list(sep); 316 } 317 } 318 319 void 320 rl_clear_ip_list(struct servtab *sep) 321 { 322 while (!SLIST_EMPTY(&sep->se_rl_ip_list)) { 323 struct rl_ip_node *node = SLIST_FIRST(&sep->se_rl_ip_list); 324 SLIST_REMOVE_HEAD(&sep->se_rl_ip_list, entries); 325 free(node); 326 } 327 } 328 329 /* Get the node associated with addr, or NULL */ 330 static struct rl_ip_node * 331 rl_try_get_ip(struct servtab *sep, union addr *addr) 332 { 333 334 struct rl_ip_node *cur; 335 SLIST_FOREACH(cur, &sep->se_rl_ip_list, entries) { 336 if (rl_ip_eq(sep, addr, cur)) { 337 return cur; 338 } 339 } 340 341 return NULL; 342 } 343 344 /* Return true if passed service rate limiting checks, false if blocked */ 345 static bool 346 rl_process_service_max(struct servtab *sep, int ctrl, time_t *now) 347 { 348 if (sep->se_count >= sep->se_service_max) { 349 if (*now == -1) { 350 /* Only get the clock time if we didn't already */ 351 *now = rl_time(); 352 } 353 354 if (*now - sep->se_time > CNT_INTVL) { 355 rl_reset(sep, *now); 356 } else { 357 syslog(LOG_ERR, SERV_FMT 358 ": max spawn rate (%zu in %ji seconds) " 359 "already met; closing for %ju seconds", 360 SERV_PARAMS(sep), 361 sep->se_service_max, 362 (intmax_t)CNT_INTVL, 363 (uintmax_t)RETRYTIME); 364 DPRINTF(SERV_FMT 365 ": max spawn rate (%zu in %ji seconds) " 366 "already met; closing for %ju seconds", 367 SERV_PARAMS(sep), 368 sep->se_service_max, 369 (intmax_t)CNT_INTVL, 370 (uintmax_t)RETRYTIME); 371 372 rl_drop_connection(sep, ctrl); 373 374 /* Close the server for 10 minutes */ 375 close_sep(sep); 376 if (!timingout) { 377 timingout = true; 378 alarm(RETRYTIME); 379 } 380 381 return false; 382 } 383 } 384 return true; 385 } 386 387 /* Return true if passed IP rate limiting checks, false if blocked */ 388 static bool 389 rl_process_ip_max(struct servtab *sep, int ctrl, time_t *now) { 390 if (sep->se_ip_max != SERVTAB_UNSPEC_SIZE_T) { 391 struct rl_ip_node *node; 392 union addr addr; 393 394 rl_get_name(sep, ctrl, &addr); 395 node = rl_try_get_ip(sep, &addr); 396 if (node == NULL) { 397 node = rl_add(sep, &addr); 398 if (node == NULL) { 399 /* If rl_add can't allocate, reject request */ 400 DPRINTF("Cannot allocate rl_ip_node"); 401 return false; 402 } 403 } 404 #ifdef DEBUG_ENABLE 405 else { 406 /* 407 * in a separate function to prevent large stack 408 * frame 409 */ 410 rl_print_found_node(sep, node); 411 } 412 #endif 413 414 DPRINTF( 415 SERV_FMT ": se_ip_max %zu and ip_count %zu", 416 SERV_PARAMS(sep), sep->se_ip_max, node->count); 417 418 if (node->count >= sep->se_ip_max) { 419 if (*now == -1) { 420 *now = rl_time(); 421 } 422 423 if (*now - sep->se_time > CNT_INTVL) { 424 rl_reset(sep, *now); 425 node = rl_add(sep, &addr); 426 if (node == NULL) { 427 DPRINTF("Cannot allocate rl_ip_node"); 428 return false; 429 } 430 } else { 431 if (debug && node->count == sep->se_ip_max) { 432 /* 433 * Only log first failed request to 434 * prevent DoS attack writing to system 435 * log 436 */ 437 rl_log_address_exceed(sep, node); 438 } else { 439 DPRINTF(SERV_FMT 440 ": service not started", 441 SERV_PARAMS(sep)); 442 } 443 444 rl_drop_connection(sep, ctrl); 445 /* 446 * Increment so debug-syslog message will 447 * trigger only once 448 */ 449 if (node->count < SIZE_MAX) { 450 node->count++; 451 } 452 return false; 453 } 454 } 455 node->count++; 456 } 457 return true; 458 } 459 460 static bool 461 rl_ip_eq(struct servtab *sep, union addr *addr, struct rl_ip_node *cur) { 462 switch(sep->se_family) { 463 case AF_INET: 464 if (addr->ipv4_addr.s_addr == cur->ipv4_addr.s_addr) { 465 return true; 466 } 467 break; 468 #ifdef INET6 469 case AF_INET6: 470 if (rl_ipv6_eq(&addr->ipv6_addr, &cur->ipv6_addr)) { 471 return true; 472 } 473 break; 474 #endif 475 default: 476 if (strncmp(cur->other_addr, addr->other_addr, NI_MAXHOST) 477 == 0) { 478 return true; 479 } 480 break; 481 } 482 return false; 483 } 484 485 #ifdef INET6 486 static bool 487 rl_ipv6_eq(struct in6_addr *a, struct in6_addr *b) 488 { 489 #if UINTMAX_MAX >= UINT64_MAX 490 { /* requires 8 byte aligned structs */ 491 uint64_t *ap = (uint64_t *)a->s6_addr; 492 uint64_t *bp = (uint64_t *)b->s6_addr; 493 return (ap[0] == bp[0]) & (ap[1] == bp[1]); 494 } 495 #else 496 { /* requires 4 byte aligned structs */ 497 uint32_t *ap = (uint32_t *)a->s6_addr; 498 uint32_t *bp = (uint32_t *)b->s6_addr; 499 return ap[0] == bp[0] && ap[1] == bp[1] && 500 ap[2] == bp[2] && ap[3] == bp[3]; 501 } 502 #endif 503 } 504 #endif 505 506 static const char * 507 rl_node_tostring(struct servtab *sep, struct rl_ip_node *node, 508 char buffer[NI_MAXHOST]) 509 { 510 switch (sep->se_family) { 511 case AF_INET: 512 #ifdef INET6 513 case AF_INET6: 514 #endif 515 /* ipv4_addr/ipv6_addr share same address */ 516 return inet_ntop(sep->se_family, (void*)&node->ipv4_addr, 517 (char*)buffer, NI_MAXHOST); 518 default: 519 return (char *)&node->other_addr; 520 } 521 } 522 523 #ifdef DEBUG_ENABLE 524 /* Separate function due to large buffer size */ 525 static void 526 rl_print_found_node(struct servtab *sep, struct rl_ip_node *node) 527 { 528 char buffer[NI_MAXHOST]; 529 DPRINTF(SERV_FMT ": found record for address '%s'", 530 SERV_PARAMS(sep), rl_node_tostring(sep, node, buffer)); 531 } 532 #endif 533 534 /* Separate function due to large buffer sie */ 535 static void 536 rl_log_address_exceed(struct servtab *sep, struct rl_ip_node *node) 537 { 538 char buffer[NI_MAXHOST]; 539 const char * name = rl_node_tostring(sep, node, buffer); 540 syslog(LOG_ERR, SERV_FMT 541 ": max ip spawn rate (%zu in " 542 "%ji seconds) for " 543 "'%." TOSTRING(NI_MAXHOST) "s' " 544 "already met; service not started", 545 SERV_PARAMS(sep), 546 sep->se_ip_max, 547 (intmax_t)CNT_INTVL, 548 name); 549 DPRINTF(SERV_FMT 550 ": max ip spawn rate (%zu in " 551 "%ji seconds) for " 552 "'%." TOSTRING(NI_MAXHOST) "s' " 553 "already met; service not started", 554 SERV_PARAMS(sep), 555 sep->se_ip_max, 556 (intmax_t)CNT_INTVL, 557 name); 558 } 559