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