1 /* 2 * Copyright (c) 2002-2003,2010 Luigi Rizzo 3 * 4 * Redistribution and use in source forms, with and without modification, 5 * are permitted provided that this entire comment appears intact. 6 * 7 * Redistribution in binary form may occur without any restrictions. 8 * Obviously, it would be nice if you gave credit where credit is due 9 * but requiring it would be too onerous. 10 * 11 * This software is provided ``AS IS'' without any warranties of any kind. 12 * 13 * $FreeBSD$ 14 * 15 * dummynet support 16 */ 17 18 #include <sys/types.h> 19 #include <sys/socket.h> 20 /* XXX there are several sysctl leftover here */ 21 #include <sys/sysctl.h> 22 23 #include "ipfw2.h" 24 25 #include <ctype.h> 26 #include <err.h> 27 #include <errno.h> 28 #include <libutil.h> 29 #include <netdb.h> 30 #include <stdio.h> 31 #include <stdlib.h> 32 #include <string.h> 33 #include <sysexits.h> 34 35 #include <net/if.h> 36 #include <netinet/in.h> 37 #include <netinet/ip_fw.h> 38 #include <netinet/ip_dummynet.h> 39 #include <arpa/inet.h> /* inet_ntoa */ 40 41 42 static struct _s_x dummynet_params[] = { 43 { "plr", TOK_PLR }, 44 { "noerror", TOK_NOERROR }, 45 { "buckets", TOK_BUCKETS }, 46 { "dst-ip", TOK_DSTIP }, 47 { "src-ip", TOK_SRCIP }, 48 { "dst-port", TOK_DSTPORT }, 49 { "src-port", TOK_SRCPORT }, 50 { "proto", TOK_PROTO }, 51 { "weight", TOK_WEIGHT }, 52 { "lmax", TOK_LMAX }, 53 { "maxlen", TOK_LMAX }, 54 { "all", TOK_ALL }, 55 { "mask", TOK_MASK }, /* alias for both */ 56 { "sched_mask", TOK_SCHED_MASK }, 57 { "flow_mask", TOK_FLOW_MASK }, 58 { "droptail", TOK_DROPTAIL }, 59 { "ecn", TOK_ECN }, 60 { "red", TOK_RED }, 61 { "gred", TOK_GRED }, 62 { "bw", TOK_BW }, 63 { "bandwidth", TOK_BW }, 64 { "delay", TOK_DELAY }, 65 { "link", TOK_LINK }, 66 { "pipe", TOK_PIPE }, 67 { "queue", TOK_QUEUE }, 68 { "flowset", TOK_FLOWSET }, 69 { "sched", TOK_SCHED }, 70 { "pri", TOK_PRI }, 71 { "priority", TOK_PRI }, 72 { "type", TOK_TYPE }, 73 { "flow-id", TOK_FLOWID}, 74 { "dst-ipv6", TOK_DSTIP6}, 75 { "dst-ip6", TOK_DSTIP6}, 76 { "src-ipv6", TOK_SRCIP6}, 77 { "src-ip6", TOK_SRCIP6}, 78 { "profile", TOK_PROFILE}, 79 { "burst", TOK_BURST}, 80 { "dummynet-params", TOK_NULL }, 81 { NULL, 0 } /* terminator */ 82 }; 83 84 #define O_NEXT(p, len) ((void *)((char *)p + len)) 85 86 static void 87 oid_fill(struct dn_id *oid, int len, int type, uintptr_t id) 88 { 89 oid->len = len; 90 oid->type = type; 91 oid->subtype = 0; 92 oid->id = id; 93 } 94 95 /* make room in the buffer and move the pointer forward */ 96 static void * 97 o_next(struct dn_id **o, int len, int type) 98 { 99 struct dn_id *ret = *o; 100 oid_fill(ret, len, type, 0); 101 *o = O_NEXT(*o, len); 102 return ret; 103 } 104 105 #if 0 106 static int 107 sort_q(void *arg, const void *pa, const void *pb) 108 { 109 int rev = (co.do_sort < 0); 110 int field = rev ? -co.do_sort : co.do_sort; 111 long long res = 0; 112 const struct dn_flow_queue *a = pa; 113 const struct dn_flow_queue *b = pb; 114 115 switch (field) { 116 case 1: /* pkts */ 117 res = a->len - b->len; 118 break; 119 case 2: /* bytes */ 120 res = a->len_bytes - b->len_bytes; 121 break; 122 123 case 3: /* tot pkts */ 124 res = a->tot_pkts - b->tot_pkts; 125 break; 126 127 case 4: /* tot bytes */ 128 res = a->tot_bytes - b->tot_bytes; 129 break; 130 } 131 if (res < 0) 132 res = -1; 133 if (res > 0) 134 res = 1; 135 return (int)(rev ? res : -res); 136 } 137 #endif 138 139 /* print a mask and header for the subsequent list of flows */ 140 static void 141 print_mask(struct ipfw_flow_id *id) 142 { 143 if (!IS_IP6_FLOW_ID(id)) { 144 printf(" " 145 "mask: %s 0x%02x 0x%08x/0x%04x -> 0x%08x/0x%04x\n", 146 id->extra ? "queue," : "", 147 id->proto, 148 id->src_ip, id->src_port, 149 id->dst_ip, id->dst_port); 150 } else { 151 char buf[255]; 152 printf("\n mask: %sproto: 0x%02x, flow_id: 0x%08x, ", 153 id->extra ? "queue," : "", 154 id->proto, id->flow_id6); 155 inet_ntop(AF_INET6, &(id->src_ip6), buf, sizeof(buf)); 156 printf("%s/0x%04x -> ", buf, id->src_port); 157 inet_ntop(AF_INET6, &(id->dst_ip6), buf, sizeof(buf)); 158 printf("%s/0x%04x\n", buf, id->dst_port); 159 } 160 } 161 162 static void 163 print_header(struct ipfw_flow_id *id) 164 { 165 if (!IS_IP6_FLOW_ID(id)) 166 printf("BKT Prot ___Source IP/port____ " 167 "____Dest. IP/port____ " 168 "Tot_pkt/bytes Pkt/Byte Drp\n"); 169 else 170 printf("BKT ___Prot___ _flow-id_ " 171 "______________Source IPv6/port_______________ " 172 "_______________Dest. IPv6/port_______________ " 173 "Tot_pkt/bytes Pkt/Byte Drp\n"); 174 } 175 176 static void 177 list_flow(struct dn_flow *ni, int *print) 178 { 179 char buff[255]; 180 struct protoent *pe = NULL; 181 struct in_addr ina; 182 struct ipfw_flow_id *id = &ni->fid; 183 184 if (*print) { 185 print_header(&ni->fid); 186 *print = 0; 187 } 188 pe = getprotobynumber(id->proto); 189 /* XXX: Should check for IPv4 flows */ 190 printf("%3u%c", (ni->oid.id) & 0xff, 191 id->extra ? '*' : ' '); 192 if (!IS_IP6_FLOW_ID(id)) { 193 if (pe) 194 printf("%-4s ", pe->p_name); 195 else 196 printf("%4u ", id->proto); 197 ina.s_addr = htonl(id->src_ip); 198 printf("%15s/%-5d ", 199 inet_ntoa(ina), id->src_port); 200 ina.s_addr = htonl(id->dst_ip); 201 printf("%15s/%-5d ", 202 inet_ntoa(ina), id->dst_port); 203 } else { 204 /* Print IPv6 flows */ 205 if (pe != NULL) 206 printf("%9s ", pe->p_name); 207 else 208 printf("%9u ", id->proto); 209 printf("%7d %39s/%-5d ", id->flow_id6, 210 inet_ntop(AF_INET6, &(id->src_ip6), buff, sizeof(buff)), 211 id->src_port); 212 printf(" %39s/%-5d ", 213 inet_ntop(AF_INET6, &(id->dst_ip6), buff, sizeof(buff)), 214 id->dst_port); 215 } 216 pr_u64(&ni->tot_pkts, 4); 217 pr_u64(&ni->tot_bytes, 8); 218 printf("%2u %4u %3u\n", 219 ni->length, ni->len_bytes, ni->drops); 220 } 221 222 static void 223 print_flowset_parms(struct dn_fs *fs, char *prefix) 224 { 225 int l; 226 char qs[30]; 227 char plr[30]; 228 char red[90]; /* Display RED parameters */ 229 230 l = fs->qsize; 231 if (fs->flags & DN_QSIZE_BYTES) { 232 if (l >= 8192) 233 sprintf(qs, "%d KB", l / 1024); 234 else 235 sprintf(qs, "%d B", l); 236 } else 237 sprintf(qs, "%3d sl.", l); 238 if (fs->plr) 239 sprintf(plr, "plr %f", 1.0 * fs->plr / (double)(0x7fffffff)); 240 else 241 plr[0] = '\0'; 242 243 if (fs->flags & DN_IS_RED) { /* RED parameters */ 244 sprintf(red, 245 "\n\t %cRED w_q %f min_th %d max_th %d max_p %f", 246 (fs->flags & DN_IS_GENTLE_RED) ? 'G' : ' ', 247 1.0 * fs->w_q / (double)(1 << SCALE_RED), 248 fs->min_th, 249 fs->max_th, 250 1.0 * fs->max_p / (double)(1 << SCALE_RED)); 251 if (fs->flags & DN_IS_ECN) 252 strncat(red, " (ecn)", 6); 253 } else 254 sprintf(red, "droptail"); 255 256 if (prefix[0]) { 257 printf("%s %s%s %d queues (%d buckets) %s\n", 258 prefix, qs, plr, fs->oid.id, fs->buckets, red); 259 prefix[0] = '\0'; 260 } else { 261 printf("q%05d %s%s %d flows (%d buckets) sched %d " 262 "weight %d lmax %d pri %d %s\n", 263 fs->fs_nr, qs, plr, fs->oid.id, fs->buckets, 264 fs->sched_nr, fs->par[0], fs->par[1], fs->par[2], red); 265 if (fs->flags & DN_HAVE_MASK) 266 print_mask(&fs->flow_mask); 267 } 268 } 269 270 static void 271 print_extra_delay_parms(struct dn_profile *p) 272 { 273 double loss; 274 if (p->samples_no <= 0) 275 return; 276 277 loss = p->loss_level; 278 loss /= p->samples_no; 279 printf("\t profile: name \"%s\" loss %f samples %d\n", 280 p->name, loss, p->samples_no); 281 } 282 283 static void 284 flush_buf(char *buf) 285 { 286 if (buf[0]) 287 printf("%s\n", buf); 288 buf[0] = '\0'; 289 } 290 291 /* 292 * generic list routine. We expect objects in a specific order, i.e. 293 * PIPES AND SCHEDULERS: 294 * link; scheduler; internal flowset if any; instances 295 * we can tell a pipe from the number. 296 * 297 * FLOWSETS: 298 * flowset; queues; 299 * link i (int queue); scheduler i; si(i) { flowsets() : queues } 300 */ 301 static void 302 list_pipes(struct dn_id *oid, struct dn_id *end) 303 { 304 char buf[160]; /* pending buffer */ 305 int toPrint = 1; /* print header */ 306 307 buf[0] = '\0'; 308 for (; oid != end; oid = O_NEXT(oid, oid->len)) { 309 if (oid->len < sizeof(*oid)) 310 errx(1, "invalid oid len %d\n", oid->len); 311 312 switch (oid->type) { 313 default: 314 flush_buf(buf); 315 printf("unrecognized object %d size %d\n", oid->type, oid->len); 316 break; 317 case DN_TEXT: /* list of attached flowsets */ 318 { 319 int i, l; 320 struct { 321 struct dn_id id; 322 uint32_t p[0]; 323 } *d = (void *)oid; 324 l = (oid->len - sizeof(*oid))/sizeof(d->p[0]); 325 if (l == 0) 326 break; 327 printf(" Children flowsets: "); 328 for (i = 0; i < l; i++) 329 printf("%u ", d->p[i]); 330 printf("\n"); 331 break; 332 } 333 case DN_CMD_GET: 334 if (co.verbose) 335 printf("answer for cmd %d, len %d\n", oid->type, oid->id); 336 break; 337 case DN_SCH: { 338 struct dn_sch *s = (struct dn_sch *)oid; 339 flush_buf(buf); 340 printf(" sched %d type %s flags 0x%x %d buckets %d active\n", 341 s->sched_nr, 342 s->name, s->flags, s->buckets, s->oid.id); 343 if (s->flags & DN_HAVE_MASK) 344 print_mask(&s->sched_mask); 345 } 346 break; 347 348 case DN_FLOW: 349 list_flow((struct dn_flow *)oid, &toPrint); 350 break; 351 352 case DN_LINK: { 353 struct dn_link *p = (struct dn_link *)oid; 354 double b = p->bandwidth; 355 char bwbuf[30]; 356 char burst[5 + 7]; 357 358 /* This starts a new object so flush buffer */ 359 flush_buf(buf); 360 /* data rate */ 361 if (b == 0) 362 sprintf(bwbuf, "unlimited "); 363 else if (b >= 1000000) 364 sprintf(bwbuf, "%7.3f Mbit/s", b/1000000); 365 else if (b >= 1000) 366 sprintf(bwbuf, "%7.3f Kbit/s", b/1000); 367 else 368 sprintf(bwbuf, "%7.3f bit/s ", b); 369 370 if (humanize_number(burst, sizeof(burst), p->burst, 371 "", HN_AUTOSCALE, 0) < 0 || co.verbose) 372 sprintf(burst, "%d", (int)p->burst); 373 sprintf(buf, "%05d: %s %4d ms burst %s", 374 p->link_nr % DN_MAX_ID, bwbuf, p->delay, burst); 375 } 376 break; 377 378 case DN_FS: 379 print_flowset_parms((struct dn_fs *)oid, buf); 380 break; 381 case DN_PROFILE: 382 flush_buf(buf); 383 print_extra_delay_parms((struct dn_profile *)oid); 384 } 385 flush_buf(buf); // XXX does it really go here ? 386 } 387 } 388 389 /* 390 * Delete pipe, queue or scheduler i 391 */ 392 int 393 ipfw_delete_pipe(int do_pipe, int i) 394 { 395 struct { 396 struct dn_id oid; 397 uintptr_t a[1]; /* add more if we want a list */ 398 } cmd; 399 oid_fill((void *)&cmd, sizeof(cmd), DN_CMD_DELETE, DN_API_VERSION); 400 cmd.oid.subtype = (do_pipe == 1) ? DN_LINK : 401 ( (do_pipe == 2) ? DN_FS : DN_SCH); 402 cmd.a[0] = i; 403 i = do_cmd(IP_DUMMYNET3, &cmd, cmd.oid.len); 404 if (i) { 405 i = 1; 406 warn("rule %u: setsockopt(IP_DUMMYNET_DEL)", i); 407 } 408 return i; 409 } 410 411 /* 412 * Code to parse delay profiles. 413 * 414 * Some link types introduce extra delays in the transmission 415 * of a packet, e.g. because of MAC level framing, contention on 416 * the use of the channel, MAC level retransmissions and so on. 417 * From our point of view, the channel is effectively unavailable 418 * for this extra time, which is constant or variable depending 419 * on the link type. Additionally, packets may be dropped after this 420 * time (e.g. on a wireless link after too many retransmissions). 421 * We can model the additional delay with an empirical curve 422 * that represents its distribution. 423 * 424 * cumulative probability 425 * 1.0 ^ 426 * | 427 * L +-- loss-level x 428 * | ****** 429 * | * 430 * | ***** 431 * | * 432 * | ** 433 * | * 434 * +-------*-------------------> 435 * delay 436 * 437 * The empirical curve may have both vertical and horizontal lines. 438 * Vertical lines represent constant delay for a range of 439 * probabilities; horizontal lines correspond to a discontinuty 440 * in the delay distribution: the link will use the largest delay 441 * for a given probability. 442 * 443 * To pass the curve to dummynet, we must store the parameters 444 * in a file as described below, and issue the command 445 * 446 * ipfw pipe <n> config ... bw XXX profile <filename> ... 447 * 448 * The file format is the following, with whitespace acting as 449 * a separator and '#' indicating the beginning a comment: 450 * 451 * samples N 452 * the number of samples used in the internal 453 * representation (2..1024; default 100); 454 * 455 * loss-level L 456 * The probability above which packets are lost. 457 * (0.0 <= L <= 1.0, default 1.0 i.e. no loss); 458 * 459 * name identifier 460 * Optional a name (listed by "ipfw pipe show") 461 * to identify the distribution; 462 * 463 * "delay prob" | "prob delay" 464 * One of these two lines is mandatory and defines 465 * the format of the following lines with data points. 466 * 467 * XXX YYY 468 * 2 or more lines representing points in the curve, 469 * with either delay or probability first, according 470 * to the chosen format. 471 * The unit for delay is milliseconds. 472 * 473 * Data points does not need to be ordered or equal to the number 474 * specified in the "samples" line. ipfw will sort and interpolate 475 * the curve as needed. 476 * 477 * Example of a profile file: 478 479 name bla_bla_bla 480 samples 100 481 loss-level 0.86 482 prob delay 483 0 200 # minimum overhead is 200ms 484 0.5 200 485 0.5 300 486 0.8 1000 487 0.9 1300 488 1 1300 489 490 * Internally, we will convert the curve to a fixed number of 491 * samples, and when it is time to transmit a packet we will 492 * model the extra delay as extra bits in the packet. 493 * 494 */ 495 496 #define ED_MAX_LINE_LEN 256+ED_MAX_NAME_LEN 497 #define ED_TOK_SAMPLES "samples" 498 #define ED_TOK_LOSS "loss-level" 499 #define ED_TOK_NAME "name" 500 #define ED_TOK_DELAY "delay" 501 #define ED_TOK_PROB "prob" 502 #define ED_TOK_BW "bw" 503 #define ED_SEPARATORS " \t\n" 504 #define ED_MIN_SAMPLES_NO 2 505 506 /* 507 * returns 1 if s is a non-negative number, with at least one '.' 508 */ 509 static int 510 is_valid_number(const char *s) 511 { 512 int i, dots_found = 0; 513 int len = strlen(s); 514 515 for (i = 0; i<len; ++i) 516 if (!isdigit(s[i]) && (s[i] !='.' || ++dots_found > 1)) 517 return 0; 518 return 1; 519 } 520 521 /* 522 * Take as input a string describing a bandwidth value 523 * and return the numeric bandwidth value. 524 * set clocking interface or bandwidth value 525 */ 526 static void 527 read_bandwidth(char *arg, int *bandwidth, char *if_name, int namelen) 528 { 529 if (*bandwidth != -1) 530 warnx("duplicate token, override bandwidth value!"); 531 532 if (arg[0] >= 'a' && arg[0] <= 'z') { 533 if (!if_name) { 534 errx(1, "no if support"); 535 } 536 if (namelen >= IFNAMSIZ) 537 warn("interface name truncated"); 538 namelen--; 539 /* interface name */ 540 strncpy(if_name, arg, namelen); 541 if_name[namelen] = '\0'; 542 *bandwidth = 0; 543 } else { /* read bandwidth value */ 544 int bw; 545 char *end = NULL; 546 547 bw = strtoul(arg, &end, 0); 548 if (*end == 'K' || *end == 'k') { 549 end++; 550 bw *= 1000; 551 } else if (*end == 'M' || *end == 'm') { 552 end++; 553 bw *= 1000000; 554 } 555 if ((*end == 'B' && 556 _substrcmp2(end, "Bi", "Bit/s") != 0) || 557 _substrcmp2(end, "by", "bytes") == 0) 558 bw *= 8; 559 560 if (bw < 0) 561 errx(EX_DATAERR, "bandwidth too large"); 562 563 *bandwidth = bw; 564 if (if_name) 565 if_name[0] = '\0'; 566 } 567 } 568 569 struct point { 570 double prob; 571 double delay; 572 }; 573 574 static int 575 compare_points(const void *vp1, const void *vp2) 576 { 577 const struct point *p1 = vp1; 578 const struct point *p2 = vp2; 579 double res = 0; 580 581 res = p1->prob - p2->prob; 582 if (res == 0) 583 res = p1->delay - p2->delay; 584 if (res < 0) 585 return -1; 586 else if (res > 0) 587 return 1; 588 else 589 return 0; 590 } 591 592 #define ED_EFMT(s) EX_DATAERR,"error in %s at line %d: "#s,filename,lineno 593 594 static void 595 load_extra_delays(const char *filename, struct dn_profile *p, 596 struct dn_link *link) 597 { 598 char line[ED_MAX_LINE_LEN]; 599 FILE *f; 600 int lineno = 0; 601 int i; 602 603 int samples = -1; 604 double loss = -1.0; 605 char profile_name[ED_MAX_NAME_LEN]; 606 int delay_first = -1; 607 int do_points = 0; 608 struct point points[ED_MAX_SAMPLES_NO]; 609 int points_no = 0; 610 611 /* XXX link never NULL? */ 612 p->link_nr = link->link_nr; 613 614 profile_name[0] = '\0'; 615 f = fopen(filename, "r"); 616 if (f == NULL) 617 err(EX_UNAVAILABLE, "fopen: %s", filename); 618 619 while (fgets(line, ED_MAX_LINE_LEN, f)) { /* read commands */ 620 char *s, *cur = line, *name = NULL, *arg = NULL; 621 622 ++lineno; 623 624 /* parse the line */ 625 while (cur) { 626 s = strsep(&cur, ED_SEPARATORS); 627 if (s == NULL || *s == '#') 628 break; 629 if (*s == '\0') 630 continue; 631 if (arg) 632 errx(ED_EFMT("too many arguments")); 633 if (name == NULL) 634 name = s; 635 else 636 arg = s; 637 } 638 if (name == NULL) /* empty line */ 639 continue; 640 if (arg == NULL) 641 errx(ED_EFMT("missing arg for %s"), name); 642 643 if (!strcasecmp(name, ED_TOK_SAMPLES)) { 644 if (samples > 0) 645 errx(ED_EFMT("duplicate ``samples'' line")); 646 if (atoi(arg) <=0) 647 errx(ED_EFMT("invalid number of samples")); 648 samples = atoi(arg); 649 if (samples>ED_MAX_SAMPLES_NO) 650 errx(ED_EFMT("too many samples, maximum is %d"), 651 ED_MAX_SAMPLES_NO); 652 do_points = 0; 653 } else if (!strcasecmp(name, ED_TOK_BW)) { 654 char buf[IFNAMSIZ]; 655 read_bandwidth(arg, &link->bandwidth, buf, sizeof(buf)); 656 } else if (!strcasecmp(name, ED_TOK_LOSS)) { 657 if (loss != -1.0) 658 errx(ED_EFMT("duplicated token: %s"), name); 659 if (!is_valid_number(arg)) 660 errx(ED_EFMT("invalid %s"), arg); 661 loss = atof(arg); 662 if (loss > 1) 663 errx(ED_EFMT("%s greater than 1.0"), name); 664 do_points = 0; 665 } else if (!strcasecmp(name, ED_TOK_NAME)) { 666 if (profile_name[0] != '\0') 667 errx(ED_EFMT("duplicated token: %s"), name); 668 strncpy(profile_name, arg, sizeof(profile_name) - 1); 669 profile_name[sizeof(profile_name)-1] = '\0'; 670 do_points = 0; 671 } else if (!strcasecmp(name, ED_TOK_DELAY)) { 672 if (do_points) 673 errx(ED_EFMT("duplicated token: %s"), name); 674 delay_first = 1; 675 do_points = 1; 676 } else if (!strcasecmp(name, ED_TOK_PROB)) { 677 if (do_points) 678 errx(ED_EFMT("duplicated token: %s"), name); 679 delay_first = 0; 680 do_points = 1; 681 } else if (do_points) { 682 if (!is_valid_number(name) || !is_valid_number(arg)) 683 errx(ED_EFMT("invalid point found")); 684 if (delay_first) { 685 points[points_no].delay = atof(name); 686 points[points_no].prob = atof(arg); 687 } else { 688 points[points_no].delay = atof(arg); 689 points[points_no].prob = atof(name); 690 } 691 if (points[points_no].prob > 1.0) 692 errx(ED_EFMT("probability greater than 1.0")); 693 ++points_no; 694 } else { 695 errx(ED_EFMT("unrecognised command '%s'"), name); 696 } 697 } 698 699 fclose (f); 700 701 if (samples == -1) { 702 warnx("'%s' not found, assuming 100", ED_TOK_SAMPLES); 703 samples = 100; 704 } 705 706 if (loss == -1.0) { 707 warnx("'%s' not found, assuming no loss", ED_TOK_LOSS); 708 loss = 1; 709 } 710 711 /* make sure that there are enough points. */ 712 if (points_no < ED_MIN_SAMPLES_NO) 713 errx(ED_EFMT("too few samples, need at least %d"), 714 ED_MIN_SAMPLES_NO); 715 716 qsort(points, points_no, sizeof(struct point), compare_points); 717 718 /* interpolation */ 719 for (i = 0; i<points_no-1; ++i) { 720 double y1 = points[i].prob * samples; 721 double x1 = points[i].delay; 722 double y2 = points[i+1].prob * samples; 723 double x2 = points[i+1].delay; 724 725 int ix = y1; 726 int stop = y2; 727 728 if (x1 == x2) { 729 for (; ix<stop; ++ix) 730 p->samples[ix] = x1; 731 } else { 732 double m = (y2-y1)/(x2-x1); 733 double c = y1 - m*x1; 734 for (; ix<stop ; ++ix) 735 p->samples[ix] = (ix - c)/m; 736 } 737 } 738 p->samples_no = samples; 739 p->loss_level = loss * samples; 740 strncpy(p->name, profile_name, sizeof(p->name)); 741 } 742 743 /* 744 * configuration of pipes, schedulers, flowsets. 745 * When we configure a new scheduler, an empty pipe is created, so: 746 * 747 * do_pipe = 1 -> "pipe N config ..." only for backward compatibility 748 * sched N+Delta type fifo sched_mask ... 749 * pipe N+Delta <parameters> 750 * flowset N+Delta pipe N+Delta (no parameters) 751 * sched N type wf2q+ sched_mask ... 752 * pipe N <parameters> 753 * 754 * do_pipe = 2 -> flowset N config 755 * flowset N parameters 756 * 757 * do_pipe = 3 -> sched N config 758 * sched N parameters (default no pipe) 759 * optional Pipe N config ... 760 * pipe ==> 761 */ 762 void 763 ipfw_config_pipe(int ac, char **av) 764 { 765 int i; 766 u_int j; 767 char *end; 768 struct dn_id *buf, *base; 769 struct dn_sch *sch = NULL; 770 struct dn_link *p = NULL; 771 struct dn_fs *fs = NULL; 772 struct dn_profile *pf = NULL; 773 struct ipfw_flow_id *mask = NULL; 774 int lmax; 775 uint32_t _foo = 0, *flags = &_foo , *buckets = &_foo; 776 777 /* 778 * allocate space for 1 header, 779 * 1 scheduler, 1 link, 1 flowset, 1 profile 780 */ 781 lmax = sizeof(struct dn_id); /* command header */ 782 lmax += sizeof(struct dn_sch) + sizeof(struct dn_link) + 783 sizeof(struct dn_fs) + sizeof(struct dn_profile); 784 785 av++; ac--; 786 /* Pipe number */ 787 if (ac && isdigit(**av)) { 788 i = atoi(*av); av++; ac--; 789 } else 790 i = -1; 791 if (i <= 0) 792 errx(EX_USAGE, "need a pipe/flowset/sched number"); 793 base = buf = safe_calloc(1, lmax); 794 /* all commands start with a 'CONFIGURE' and a version */ 795 o_next(&buf, sizeof(struct dn_id), DN_CMD_CONFIG); 796 base->id = DN_API_VERSION; 797 798 switch (co.do_pipe) { 799 case 1: /* "pipe N config ..." */ 800 /* Allocate space for the WF2Q+ scheduler, its link 801 * and the FIFO flowset. Set the number, but leave 802 * the scheduler subtype and other parameters to 0 803 * so the kernel will use appropriate defaults. 804 * XXX todo: add a flag to record if a parameter 805 * is actually configured. 806 * If we do a 'pipe config' mask -> sched_mask. 807 * The FIFO scheduler and link are derived from the 808 * WF2Q+ one in the kernel. 809 */ 810 sch = o_next(&buf, sizeof(*sch), DN_SCH); 811 p = o_next(&buf, sizeof(*p), DN_LINK); 812 fs = o_next(&buf, sizeof(*fs), DN_FS); 813 814 sch->sched_nr = i; 815 sch->oid.subtype = 0; /* defaults to WF2Q+ */ 816 mask = &sch->sched_mask; 817 flags = &sch->flags; 818 buckets = &sch->buckets; 819 *flags |= DN_PIPE_CMD; 820 821 p->link_nr = i; 822 823 /* This flowset is only for the FIFO scheduler */ 824 fs->fs_nr = i + 2*DN_MAX_ID; 825 fs->sched_nr = i + DN_MAX_ID; 826 break; 827 828 case 2: /* "queue N config ... " */ 829 fs = o_next(&buf, sizeof(*fs), DN_FS); 830 fs->fs_nr = i; 831 mask = &fs->flow_mask; 832 flags = &fs->flags; 833 buckets = &fs->buckets; 834 break; 835 836 case 3: /* "sched N config ..." */ 837 sch = o_next(&buf, sizeof(*sch), DN_SCH); 838 fs = o_next(&buf, sizeof(*fs), DN_FS); 839 sch->sched_nr = i; 840 mask = &sch->sched_mask; 841 flags = &sch->flags; 842 buckets = &sch->buckets; 843 /* fs is used only with !MULTIQUEUE schedulers */ 844 fs->fs_nr = i + DN_MAX_ID; 845 fs->sched_nr = i; 846 break; 847 } 848 /* set to -1 those fields for which we want to reuse existing 849 * values from the kernel. 850 * Also, *_nr and subtype = 0 mean reuse the value from the kernel. 851 * XXX todo: support reuse of the mask. 852 */ 853 if (p) 854 p->bandwidth = -1; 855 for (j = 0; j < sizeof(fs->par)/sizeof(fs->par[0]); j++) 856 fs->par[j] = -1; 857 while (ac > 0) { 858 double d; 859 int tok = match_token(dummynet_params, *av); 860 ac--; av++; 861 862 switch(tok) { 863 case TOK_NOERROR: 864 NEED(fs, "noerror is only for pipes"); 865 fs->flags |= DN_NOERROR; 866 break; 867 868 case TOK_PLR: 869 NEED(fs, "plr is only for pipes"); 870 NEED1("plr needs argument 0..1\n"); 871 d = strtod(av[0], NULL); 872 if (d > 1) 873 d = 1; 874 else if (d < 0) 875 d = 0; 876 fs->plr = (int)(d*0x7fffffff); 877 ac--; av++; 878 break; 879 880 case TOK_QUEUE: 881 NEED(fs, "queue is only for pipes or flowsets"); 882 NEED1("queue needs queue size\n"); 883 end = NULL; 884 fs->qsize = strtoul(av[0], &end, 0); 885 if (*end == 'K' || *end == 'k') { 886 fs->flags |= DN_QSIZE_BYTES; 887 fs->qsize *= 1024; 888 } else if (*end == 'B' || 889 _substrcmp2(end, "by", "bytes") == 0) { 890 fs->flags |= DN_QSIZE_BYTES; 891 } 892 ac--; av++; 893 break; 894 895 case TOK_BUCKETS: 896 NEED(fs, "buckets is only for pipes or flowsets"); 897 NEED1("buckets needs argument\n"); 898 *buckets = strtoul(av[0], NULL, 0); 899 ac--; av++; 900 break; 901 902 case TOK_FLOW_MASK: 903 case TOK_SCHED_MASK: 904 case TOK_MASK: 905 NEED(mask, "tok_mask"); 906 NEED1("mask needs mask specifier\n"); 907 /* 908 * per-flow queue, mask is dst_ip, dst_port, 909 * src_ip, src_port, proto measured in bits 910 */ 911 912 bzero(mask, sizeof(*mask)); 913 end = NULL; 914 915 while (ac >= 1) { 916 uint32_t *p32 = NULL; 917 uint16_t *p16 = NULL; 918 uint32_t *p20 = NULL; 919 struct in6_addr *pa6 = NULL; 920 uint32_t a; 921 922 tok = match_token(dummynet_params, *av); 923 ac--; av++; 924 switch(tok) { 925 case TOK_ALL: 926 /* 927 * special case, all bits significant 928 * except 'extra' (the queue number) 929 */ 930 mask->dst_ip = ~0; 931 mask->src_ip = ~0; 932 mask->dst_port = ~0; 933 mask->src_port = ~0; 934 mask->proto = ~0; 935 n2mask(&mask->dst_ip6, 128); 936 n2mask(&mask->src_ip6, 128); 937 mask->flow_id6 = ~0; 938 *flags |= DN_HAVE_MASK; 939 goto end_mask; 940 941 case TOK_QUEUE: 942 mask->extra = ~0; 943 *flags |= DN_HAVE_MASK; 944 goto end_mask; 945 946 case TOK_DSTIP: 947 mask->addr_type = 4; 948 p32 = &mask->dst_ip; 949 break; 950 951 case TOK_SRCIP: 952 mask->addr_type = 4; 953 p32 = &mask->src_ip; 954 break; 955 956 case TOK_DSTIP6: 957 mask->addr_type = 6; 958 pa6 = &mask->dst_ip6; 959 break; 960 961 case TOK_SRCIP6: 962 mask->addr_type = 6; 963 pa6 = &mask->src_ip6; 964 break; 965 966 case TOK_FLOWID: 967 mask->addr_type = 6; 968 p20 = &mask->flow_id6; 969 break; 970 971 case TOK_DSTPORT: 972 p16 = &mask->dst_port; 973 break; 974 975 case TOK_SRCPORT: 976 p16 = &mask->src_port; 977 break; 978 979 case TOK_PROTO: 980 break; 981 982 default: 983 ac++; av--; /* backtrack */ 984 goto end_mask; 985 } 986 if (ac < 1) 987 errx(EX_USAGE, "mask: value missing"); 988 if (*av[0] == '/') { 989 a = strtoul(av[0]+1, &end, 0); 990 if (pa6 == NULL) 991 a = (a == 32) ? ~0 : (1 << a) - 1; 992 } else 993 a = strtoul(av[0], &end, 0); 994 if (p32 != NULL) 995 *p32 = a; 996 else if (p16 != NULL) { 997 if (a > 0xFFFF) 998 errx(EX_DATAERR, 999 "port mask must be 16 bit"); 1000 *p16 = (uint16_t)a; 1001 } else if (p20 != NULL) { 1002 if (a > 0xfffff) 1003 errx(EX_DATAERR, 1004 "flow_id mask must be 20 bit"); 1005 *p20 = (uint32_t)a; 1006 } else if (pa6 != NULL) { 1007 if (a > 128) 1008 errx(EX_DATAERR, 1009 "in6addr invalid mask len"); 1010 else 1011 n2mask(pa6, a); 1012 } else { 1013 if (a > 0xFF) 1014 errx(EX_DATAERR, 1015 "proto mask must be 8 bit"); 1016 mask->proto = (uint8_t)a; 1017 } 1018 if (a != 0) 1019 *flags |= DN_HAVE_MASK; 1020 ac--; av++; 1021 } /* end while, config masks */ 1022 end_mask: 1023 break; 1024 1025 case TOK_RED: 1026 case TOK_GRED: 1027 NEED1("red/gred needs w_q/min_th/max_th/max_p\n"); 1028 fs->flags |= DN_IS_RED; 1029 if (tok == TOK_GRED) 1030 fs->flags |= DN_IS_GENTLE_RED; 1031 /* 1032 * the format for parameters is w_q/min_th/max_th/max_p 1033 */ 1034 if ((end = strsep(&av[0], "/"))) { 1035 double w_q = strtod(end, NULL); 1036 if (w_q > 1 || w_q <= 0) 1037 errx(EX_DATAERR, "0 < w_q <= 1"); 1038 fs->w_q = (int) (w_q * (1 << SCALE_RED)); 1039 } 1040 if ((end = strsep(&av[0], "/"))) { 1041 fs->min_th = strtoul(end, &end, 0); 1042 if (*end == 'K' || *end == 'k') 1043 fs->min_th *= 1024; 1044 } 1045 if ((end = strsep(&av[0], "/"))) { 1046 fs->max_th = strtoul(end, &end, 0); 1047 if (*end == 'K' || *end == 'k') 1048 fs->max_th *= 1024; 1049 } 1050 if ((end = strsep(&av[0], "/"))) { 1051 double max_p = strtod(end, NULL); 1052 if (max_p > 1 || max_p < 0) 1053 errx(EX_DATAERR, "0 <= max_p <= 1"); 1054 fs->max_p = (int)(max_p * (1 << SCALE_RED)); 1055 } 1056 ac--; av++; 1057 break; 1058 1059 case TOK_ECN: 1060 fs->flags |= DN_IS_ECN; 1061 break; 1062 1063 case TOK_DROPTAIL: 1064 NEED(fs, "droptail is only for flowsets"); 1065 fs->flags &= ~(DN_IS_RED|DN_IS_GENTLE_RED); 1066 break; 1067 1068 case TOK_BW: 1069 NEED(p, "bw is only for links"); 1070 NEED1("bw needs bandwidth or interface\n"); 1071 read_bandwidth(av[0], &p->bandwidth, NULL, 0); 1072 ac--; av++; 1073 break; 1074 1075 case TOK_DELAY: 1076 NEED(p, "delay is only for links"); 1077 NEED1("delay needs argument 0..10000ms\n"); 1078 p->delay = strtoul(av[0], NULL, 0); 1079 ac--; av++; 1080 break; 1081 1082 case TOK_TYPE: { 1083 int l; 1084 NEED(sch, "type is only for schedulers"); 1085 NEED1("type needs a string"); 1086 l = strlen(av[0]); 1087 if (l == 0 || l > 15) 1088 errx(1, "type %s too long\n", av[0]); 1089 strcpy(sch->name, av[0]); 1090 sch->oid.subtype = 0; /* use string */ 1091 ac--; av++; 1092 break; 1093 } 1094 1095 case TOK_WEIGHT: 1096 NEED(fs, "weight is only for flowsets"); 1097 NEED1("weight needs argument\n"); 1098 fs->par[0] = strtol(av[0], &end, 0); 1099 ac--; av++; 1100 break; 1101 1102 case TOK_LMAX: 1103 NEED(fs, "lmax is only for flowsets"); 1104 NEED1("lmax needs argument\n"); 1105 fs->par[1] = strtol(av[0], &end, 0); 1106 ac--; av++; 1107 break; 1108 1109 case TOK_PRI: 1110 NEED(fs, "priority is only for flowsets"); 1111 NEED1("priority needs argument\n"); 1112 fs->par[2] = strtol(av[0], &end, 0); 1113 ac--; av++; 1114 break; 1115 1116 case TOK_SCHED: 1117 case TOK_PIPE: 1118 NEED(fs, "pipe/sched"); 1119 NEED1("pipe/link/sched needs number\n"); 1120 fs->sched_nr = strtoul(av[0], &end, 0); 1121 ac--; av++; 1122 break; 1123 1124 case TOK_PROFILE: 1125 NEED((!pf), "profile already set"); 1126 NEED(p, "profile"); 1127 { 1128 NEED1("extra delay needs the file name\n"); 1129 pf = o_next(&buf, sizeof(*pf), DN_PROFILE); 1130 load_extra_delays(av[0], pf, p); //XXX can't fail? 1131 --ac; ++av; 1132 } 1133 break; 1134 1135 case TOK_BURST: 1136 NEED(p, "burst"); 1137 NEED1("burst needs argument\n"); 1138 errno = 0; 1139 if (expand_number(av[0], &p->burst) < 0) 1140 if (errno != ERANGE) 1141 errx(EX_DATAERR, 1142 "burst: invalid argument"); 1143 if (errno || p->burst > (1ULL << 48) - 1) 1144 errx(EX_DATAERR, 1145 "burst: out of range (0..2^48-1)"); 1146 ac--; av++; 1147 break; 1148 1149 default: 1150 errx(EX_DATAERR, "unrecognised option ``%s''", av[-1]); 1151 } 1152 } 1153 1154 /* check validity of parameters */ 1155 if (p) { 1156 if (p->delay > 10000) 1157 errx(EX_DATAERR, "delay must be < 10000"); 1158 if (p->bandwidth == -1) 1159 p->bandwidth = 0; 1160 } 1161 if (fs) { 1162 /* XXX accept a 0 scheduler to keep the default */ 1163 if (fs->flags & DN_QSIZE_BYTES) { 1164 size_t len; 1165 long limit; 1166 1167 len = sizeof(limit); 1168 if (sysctlbyname("net.inet.ip.dummynet.pipe_byte_limit", 1169 &limit, &len, NULL, 0) == -1) 1170 limit = 1024*1024; 1171 if (fs->qsize > limit) 1172 errx(EX_DATAERR, "queue size must be < %ldB", limit); 1173 } else { 1174 size_t len; 1175 long limit; 1176 1177 len = sizeof(limit); 1178 if (sysctlbyname("net.inet.ip.dummynet.pipe_slot_limit", 1179 &limit, &len, NULL, 0) == -1) 1180 limit = 100; 1181 if (fs->qsize > limit) 1182 errx(EX_DATAERR, "2 <= queue size <= %ld", limit); 1183 } 1184 1185 if ((fs->flags & DN_IS_ECN) && !(fs->flags & DN_IS_RED)) 1186 errx(EX_USAGE, "enable red/gred for ECN"); 1187 1188 if (fs->flags & DN_IS_RED) { 1189 size_t len; 1190 int lookup_depth, avg_pkt_size; 1191 1192 if (!(fs->flags & DN_IS_ECN) && (fs->min_th >= fs->max_th)) 1193 errx(EX_DATAERR, "min_th %d must be < than max_th %d", 1194 fs->min_th, fs->max_th); 1195 else if ((fs->flags & DN_IS_ECN) && (fs->min_th > fs->max_th)) 1196 errx(EX_DATAERR, "min_th %d must be =< than max_th %d", 1197 fs->min_th, fs->max_th); 1198 1199 if (fs->max_th == 0) 1200 errx(EX_DATAERR, "max_th must be > 0"); 1201 1202 len = sizeof(int); 1203 if (sysctlbyname("net.inet.ip.dummynet.red_lookup_depth", 1204 &lookup_depth, &len, NULL, 0) == -1) 1205 lookup_depth = 256; 1206 if (lookup_depth == 0) 1207 errx(EX_DATAERR, "net.inet.ip.dummynet.red_lookup_depth" 1208 " must be greater than zero"); 1209 1210 len = sizeof(int); 1211 if (sysctlbyname("net.inet.ip.dummynet.red_avg_pkt_size", 1212 &avg_pkt_size, &len, NULL, 0) == -1) 1213 avg_pkt_size = 512; 1214 1215 if (avg_pkt_size == 0) 1216 errx(EX_DATAERR, 1217 "net.inet.ip.dummynet.red_avg_pkt_size must" 1218 " be greater than zero"); 1219 1220 #if 0 /* the following computation is now done in the kernel */ 1221 /* 1222 * Ticks needed for sending a medium-sized packet. 1223 * Unfortunately, when we are configuring a WF2Q+ queue, we 1224 * do not have bandwidth information, because that is stored 1225 * in the parent pipe, and also we have multiple queues 1226 * competing for it. So we set s=0, which is not very 1227 * correct. But on the other hand, why do we want RED with 1228 * WF2Q+ ? 1229 */ 1230 if (p.bandwidth==0) /* this is a WF2Q+ queue */ 1231 s = 0; 1232 else 1233 s = (double)ck.hz * avg_pkt_size * 8 / p.bandwidth; 1234 /* 1235 * max idle time (in ticks) before avg queue size becomes 0. 1236 * NOTA: (3/w_q) is approx the value x so that 1237 * (1-w_q)^x < 10^-3. 1238 */ 1239 w_q = ((double)fs->w_q) / (1 << SCALE_RED); 1240 idle = s * 3. / w_q; 1241 fs->lookup_step = (int)idle / lookup_depth; 1242 if (!fs->lookup_step) 1243 fs->lookup_step = 1; 1244 weight = 1 - w_q; 1245 for (t = fs->lookup_step; t > 1; --t) 1246 weight *= 1 - w_q; 1247 fs->lookup_weight = (int)(weight * (1 << SCALE_RED)); 1248 #endif /* code moved in the kernel */ 1249 } 1250 } 1251 1252 i = do_cmd(IP_DUMMYNET3, base, (char *)buf - (char *)base); 1253 1254 if (i) 1255 err(1, "setsockopt(%s)", "IP_DUMMYNET_CONFIGURE"); 1256 } 1257 1258 void 1259 dummynet_flush(void) 1260 { 1261 struct dn_id oid; 1262 oid_fill(&oid, sizeof(oid), DN_CMD_FLUSH, DN_API_VERSION); 1263 do_cmd(IP_DUMMYNET3, &oid, oid.len); 1264 } 1265 1266 /* Parse input for 'ipfw [pipe|sched|queue] show [range list]' 1267 * Returns the number of ranges, and possibly stores them 1268 * in the array v of size len. 1269 */ 1270 static int 1271 parse_range(int ac, char *av[], uint32_t *v, int len) 1272 { 1273 int n = 0; 1274 char *endptr, *s; 1275 uint32_t base[2]; 1276 1277 if (v == NULL || len < 2) { 1278 v = base; 1279 len = 2; 1280 } 1281 1282 for (s = *av; s != NULL; av++, ac--) { 1283 v[0] = strtoul(s, &endptr, 10); 1284 v[1] = (*endptr != '-') ? v[0] : 1285 strtoul(endptr+1, &endptr, 10); 1286 if (*endptr == '\0') { /* prepare for next round */ 1287 s = (ac > 0) ? *(av+1) : NULL; 1288 } else { 1289 if (*endptr != ',') { 1290 warn("invalid number: %s", s); 1291 s = ++endptr; 1292 continue; 1293 } 1294 /* continue processing from here */ 1295 s = ++endptr; 1296 ac++; 1297 av--; 1298 } 1299 if (v[1] < v[0] || 1300 v[1] >= DN_MAX_ID-1 || 1301 v[1] >= DN_MAX_ID-1) { 1302 continue; /* invalid entry */ 1303 } 1304 n++; 1305 /* translate if 'pipe list' */ 1306 if (co.do_pipe == 1) { 1307 v[0] += DN_MAX_ID; 1308 v[1] += DN_MAX_ID; 1309 } 1310 v = (n*2 < len) ? v + 2 : base; 1311 } 1312 return n; 1313 } 1314 1315 /* main entry point for dummynet list functions. co.do_pipe indicates 1316 * which function we want to support. 1317 * av may contain filtering arguments, either individual entries 1318 * or ranges, or lists (space or commas are valid separators). 1319 * Format for a range can be n1-n2 or n3 n4 n5 ... 1320 * In a range n1 must be <= n2, otherwise the range is ignored. 1321 * A number 'n4' is translate in a range 'n4-n4' 1322 * All number must be > 0 and < DN_MAX_ID-1 1323 */ 1324 void 1325 dummynet_list(int ac, char *av[], int show_counters) 1326 { 1327 struct dn_id *oid, *x = NULL; 1328 int ret, i; 1329 int n; /* # of ranges */ 1330 u_int buflen, l; 1331 u_int max_size; /* largest obj passed up */ 1332 1333 (void)show_counters; // XXX unused, but we should use it. 1334 ac--; 1335 av++; /* skip 'list' | 'show' word */ 1336 1337 n = parse_range(ac, av, NULL, 0); /* Count # of ranges. */ 1338 1339 /* Allocate space to store ranges */ 1340 l = sizeof(*oid) + sizeof(uint32_t) * n * 2; 1341 oid = safe_calloc(1, l); 1342 oid_fill(oid, l, DN_CMD_GET, DN_API_VERSION); 1343 1344 if (n > 0) /* store ranges in idx */ 1345 parse_range(ac, av, (uint32_t *)(oid + 1), n*2); 1346 /* 1347 * Compute the size of the largest object returned. If the 1348 * response leaves at least this much spare space in the 1349 * buffer, then surely the response is complete; otherwise 1350 * there might be a risk of truncation and we will need to 1351 * retry with a larger buffer. 1352 * XXX don't bother with smaller structs. 1353 */ 1354 max_size = sizeof(struct dn_fs); 1355 if (max_size < sizeof(struct dn_sch)) 1356 max_size = sizeof(struct dn_sch); 1357 if (max_size < sizeof(struct dn_flow)) 1358 max_size = sizeof(struct dn_flow); 1359 1360 switch (co.do_pipe) { 1361 case 1: 1362 oid->subtype = DN_LINK; /* list pipe */ 1363 break; 1364 case 2: 1365 oid->subtype = DN_FS; /* list queue */ 1366 break; 1367 case 3: 1368 oid->subtype = DN_SCH; /* list sched */ 1369 break; 1370 } 1371 1372 /* 1373 * Ask the kernel an estimate of the required space (result 1374 * in oid.id), unless we are requesting a subset of objects, 1375 * in which case the kernel does not give an exact answer. 1376 * In any case, space might grow in the meantime due to the 1377 * creation of new queues, so we must be prepared to retry. 1378 */ 1379 if (n > 0) { 1380 buflen = 4*1024; 1381 } else { 1382 ret = do_cmd(-IP_DUMMYNET3, oid, (uintptr_t)&l); 1383 if (ret != 0 || oid->id <= sizeof(*oid)) 1384 goto done; 1385 buflen = oid->id + max_size; 1386 oid->len = sizeof(*oid); /* restore */ 1387 } 1388 /* Try a few times, until the buffer fits */ 1389 for (i = 0; i < 20; i++) { 1390 l = buflen; 1391 x = safe_realloc(x, l); 1392 bcopy(oid, x, oid->len); 1393 ret = do_cmd(-IP_DUMMYNET3, x, (uintptr_t)&l); 1394 if (ret != 0 || x->id <= sizeof(*oid)) 1395 goto done; /* no response */ 1396 if (l + max_size <= buflen) 1397 break; /* ok */ 1398 buflen *= 2; /* double for next attempt */ 1399 } 1400 list_pipes(x, O_NEXT(x, l)); 1401 done: 1402 if (x) 1403 free(x); 1404 free(oid); 1405 } 1406