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