diff --git a/sbin/ipfw/dummynet.c b/sbin/ipfw/dummynet.c --- a/sbin/ipfw/dummynet.c +++ b/sbin/ipfw/dummynet.c @@ -471,7 +471,7 @@ { int l; char qs[30]; - char plr[30]; + char plr[40]; char red[200]; /* Display RED parameters */ l = fs->qsize; @@ -482,9 +482,17 @@ sprintf(qs, "%d B", l); } else sprintf(qs, "%3d sl.", l); - if (fs->plr) - sprintf(plr, "plr %f", 1.0 * fs->plr / (double)(0x7fffffff)); - else + if (fs->plr[0] || fs->plr[1]) { + if (fs->plr[1] == 0) + sprintf(plr, "plr %f", + 1.0 * fs->plr[0] / (double)(0x7fffffff)); + else + sprintf(plr, "plr %f,%f,%f,%f", + 1.0 * fs->plr[0] / (double)(0x7fffffff), + 1.0 * fs->plr[1] / (double)(0x7fffffff), + 1.0 * fs->plr[2] / (double)(0x7fffffff), + 1.0 * fs->plr[3] / (double)(0x7fffffff)); + } else plr[0] = '\0'; if (fs->flags & DN_IS_RED) { /* RED parameters */ @@ -1408,13 +1416,27 @@ case TOK_PLR: NEED(fs, "plr is only for pipes"); - NEED1("plr needs argument 0..1\n"); - d = strtod(av[0], NULL); - if (d > 1) - d = 1; - else if (d < 0) - d = 0; - fs->plr = (int)(d*0x7fffffff); + NEED1("plr needs one or four arguments 0..1\n"); + if ((end = strsep(&av[0], ","))) { + d = strtod(end, NULL); + d = (d < 0) ? 0 : (d <= 1) ? d : 1; + fs->plr[0] = (int)(d*0x7fffffff); + } + if ((end = strsep(&av[0], ","))) { + d = strtod(end, NULL); + d = (d < 0) ? 0 : (d <= 1) ? d : 1; + fs->plr[1] = (int)(d*0x7fffffff); + } + if ((end = strsep(&av[0], ","))) { + d = strtod(end, NULL); + d = (d < 0) ? 0 : (d <= 1) ? d : 1; + fs->plr[2] = (int)(d*0x7fffffff); + } + if ((end = strsep(&av[0], ","))) { + d = strtod(end, NULL); + d = (d < 0) ? 0 : (d <= 1) ? d : 1; + fs->plr[3] = (int)(d*0x7fffffff); + } ac--; av++; break; diff --git a/sbin/ipfw/ipfw.8 b/sbin/ipfw/ipfw.8 --- a/sbin/ipfw/ipfw.8 +++ b/sbin/ipfw/ipfw.8 @@ -1,5 +1,5 @@ .\" -.Dd September 28, 2023 +.Dd December 17, 2023 .Dt IPFW 8 .Os .Sh NAME @@ -3039,12 +3039,47 @@ loss or congestion at a remote router. .Pp .It Cm plr Ar packet-loss-rate +.It Cm plr Ar K,p,H,r Packet loss rate. Argument .Ar packet-loss-rate is a floating-point number between 0 and 1, with 0 meaning no loss, 1 meaning 100% loss. -The loss rate is internally represented on 31 bits. +.Pp +When invoked with four arguments, the simple Gilbert-Elliott +channel model with two states (Good and Bad) is used. +.Bd -literal -offset indent + r + .----------------. + v | + .------------. .------------. + | G | | B | + | drop (K) | | drop (H) | + '------------' '------------' + | ^ + '----------------' + p + +.Ed +This has the associated probabilities +.Po Ar K +and +.Ar H Pc +for the loss probability. This is different from the literature, +where this model is described with probabilities of successful +transmission k and h. However, converting from literature is +easy: +.Pp +K = 1 - k ; H = 1 - h +.Pp +This is to retain consistency within the interface and allow the +quick re-use of loss probability when giving only a single argument. +In addition the state change probabilities +.Po Ar p +and +.Ar r Pc +are given. +All of the above probabilities are internally represented on 31 bits. .Pp .It Cm queue Brq Ar slots | size Ns Cm Kbytes Queue size, in diff --git a/sys/netinet/ip_dummynet.h b/sys/netinet/ip_dummynet.h --- a/sys/netinet/ip_dummynet.h +++ b/sys/netinet/ip_dummynet.h @@ -145,7 +145,7 @@ uint32_t fs_nr; /* the flowset number */ uint32_t flags; /* userland flags */ int qsize; /* queue size in slots or bytes */ - int32_t plr; /* PLR, pkt loss rate (2^31-1 means 100%) */ + int32_t pl_state; /* packet loss state */ uint32_t buckets; /* buckets used for the queue hash table */ struct ipfw_flow_id flow_mask; @@ -168,6 +168,7 @@ int min_th ; /* minimum threshold for queue (scaled) */ int max_p ; /* maximum value for p_b (scaled) */ + int32_t plr[4]; /* PLR, pkt loss rate (2^31-1 means 100%) */ }; /* diff --git a/sys/netpfil/ipfw/ip_dn_glue.c b/sys/netpfil/ipfw/ip_dn_glue.c --- a/sys/netpfil/ipfw/ip_dn_glue.c +++ b/sys/netpfil/ipfw/ip_dn_glue.c @@ -77,35 +77,35 @@ /* Common to 7.2 and 8 */ struct dn_flow_set { - SLIST_ENTRY(dn_flow_set) next; /* linked list in a hash slot */ + SLIST_ENTRY(dn_flow_set) next; /* linked list in a hash slot */ - u_short fs_nr ; /* flow_set number */ + u_short fs_nr ; /* flow_set number */ u_short flags_fs; #define DNOLD_HAVE_FLOW_MASK 0x0001 -#define DNOLD_IS_RED 0x0002 +#define DNOLD_IS_RED 0x0002 #define DNOLD_IS_GENTLE_RED 0x0004 -#define DNOLD_QSIZE_IS_BYTES 0x0008 /* queue size is measured in bytes */ -#define DNOLD_NOERROR 0x0010 /* do not report ENOBUFS on drops */ -#define DNOLD_HAS_PROFILE 0x0020 /* the pipe has a delay profile. */ -#define DNOLD_IS_PIPE 0x4000 -#define DNOLD_IS_QUEUE 0x8000 +#define DNOLD_QSIZE_IS_BYTES 0x0008 /* queue size is measured in bytes */ +#define DNOLD_NOERROR 0x0010 /* do not report ENOBUFS on drops */ +#define DNOLD_HAS_PROFILE 0x0020 /* the pipe has a delay profile. */ +#define DNOLD_IS_PIPE 0x4000 +#define DNOLD_IS_QUEUE 0x8000 - struct dn_pipe7 *pipe ; /* pointer to parent pipe */ - u_short parent_nr ; /* parent pipe#, 0 if local to a pipe */ + struct dn_pipe7 *pipe ; /* pointer to parent pipe */ + u_short parent_nr ; /* parent pipe#, 0 if local to a pipe */ - int weight ; /* WFQ queue weight */ - int qsize ; /* queue size in slots or bytes */ - int plr ; /* pkt loss rate (2^31-1 means 100%) */ + int weight ; /* WFQ queue weight */ + int qsize ; /* queue size in slots or bytes */ + int plr[4] ; /* pkt loss rate (2^31-1 means 100%) */ struct ipfw_flow_id flow_mask ; /* hash table of queues onto this flow_set */ - int rq_size ; /* number of slots */ - int rq_elements ; /* active elements */ - struct dn_flow_queue7 **rq; /* array of rq_size entries */ + int rq_size ; /* number of slots */ + int rq_elements ; /* active elements */ + struct dn_flow_queue7 **rq ; /* array of rq_size entries */ - u_int32_t last_expired ; /* do not expire too frequently */ - int backlogged ; /* #active queues for this flowset */ + u_int32_t last_expired ; /* do not expire too frequently */ + int backlogged ; /* #active queues for this flowset */ /* RED parameters */ #define SCALE_RED 16 @@ -420,7 +420,10 @@ fs->flow_mask = f->flow_mask; fs->buckets = f->rq_size; fs->qsize = f->qsize; - fs->plr = f->plr; + fs->plr[0] = f->plr[0]; + fs->plr[1] = f->plr[1]; + fs->plr[2] = f->plr[2]; + fs->plr[3] = f->plr[3]; fs->par[0] = f->weight; fs->flags = convertflags2new(f->flags_fs); if (fs->flags & DN_IS_GENTLE_RED || fs->flags & DN_IS_RED) { @@ -645,7 +648,10 @@ fs->parent_nr = l->link_nr - DN_MAX_ID; fs->qsize = f->fs.qsize; - fs->plr = f->fs.plr; + fs->plr[0] = f->fs.plr[0]; + fs->plr[1] = f->fs.plr[1]; + fs->plr[2] = f->fs.plr[2]; + fs->plr[3] = f->fs.plr[3]; fs->w_q = f->fs.w_q; fs->max_th = f->max_th; fs->min_th = f->min_th; @@ -698,7 +704,10 @@ fs->next.sle_next = (struct dn_flow_set *)DN_IS_QUEUE; fs->fs_nr = f->fs.fs_nr; fs->qsize = f->fs.qsize; - fs->plr = f->fs.plr; + fs->plr[0] = f->fs.plr[0]; + fs->plr[1] = f->fs.plr[1]; + fs->plr[2] = f->fs.plr[2]; + fs->plr[3] = f->fs.plr[3]; fs->w_q = f->fs.w_q; fs->max_th = f->max_th; fs->min_th = f->min_th; diff --git a/sys/netpfil/ipfw/ip_dn_io.c b/sys/netpfil/ipfw/ip_dn_io.c --- a/sys/netpfil/ipfw/ip_dn_io.c +++ b/sys/netpfil/ipfw/ip_dn_io.c @@ -497,8 +497,28 @@ ni->tot_pkts++; if (drop) goto drop; - if (f->plr && random() < f->plr) - goto drop; + if (f->plr[0] || f->plr[1]) { + if (__predict_true(f->plr[1] == 0)) { + if (random() < f->plr[0]) + goto drop; + } else { + switch (f->pl_state) { + case PLR_STATE_B: + if (random() < f->plr[3]) + f->pl_state = PLR_STATE_G; + if (random() < f->plr[2]) + goto drop; + break; + case PLR_STATE_G: /* FALLTHROUGH */ + default: + if (random() < f->plr[1]) + f->pl_state = PLR_STATE_B; + if (random() < f->plr[0]) + goto drop; + break; + } + } + } if (m->m_pkthdr.rcvif != NULL) m_rcvif_serialize(m); #ifdef NEW_AQM diff --git a/sys/netpfil/ipfw/ip_dn_private.h b/sys/netpfil/ipfw/ip_dn_private.h --- a/sys/netpfil/ipfw/ip_dn_private.h +++ b/sys/netpfil/ipfw/ip_dn_private.h @@ -392,6 +392,15 @@ PROTO_IFB = 0x0c, /* layer2 + ifbridge */ }; +/* + * States for the Packet Loss Rate Gilbert-Elliott + * channel model + */ +enum { + PLR_STATE_G = 0, + PLR_STATE_B, +}; + //extern struct dn_parms V_dn_cfg; VNET_DECLARE(struct dn_parms, dn_cfg); #define V_dn_cfg VNET(dn_cfg) diff --git a/tests/sys/netpfil/common/dummynet.sh b/tests/sys/netpfil/common/dummynet.sh --- a/tests/sys/netpfil/common/dummynet.sh +++ b/tests/sys/netpfil/common/dummynet.sh @@ -517,6 +517,102 @@ firewall_cleanup $1 } +pls_basic_head() +{ + atf_set descr 'Basic dummynet packet loss rate test' + atf_set require.user root +} + +pls_basic_body() +{ + fw=$1 + firewall_init $fw + dummynet_init $fw + + epair=$(vnet_mkepair) + vnet_mkjail alcatraz ${epair}b + + ifconfig ${epair}a 192.0.2.1/24 up + jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up + + firewall_config alcatraz ${fw} \ + "ipfw" \ + "ipfw add 65432 ip from any to any" \ + "pf" \ + "pass on ${epair}b" + + # Sanity check + atf_check -s exit:0 -o match:'100 packets transmitted, 100 packets received' ping -i .1 -c 100 192.0.2.2 + + jexec alcatraz dnctl pipe 1 config plr 0.1 + + firewall_config alcatraz ${fw} \ + "ipfw" \ + "ipfw add 1000 pipe 1 ip from 192.0.2.1 to 192.0.2.2" \ + "pf" \ + "pass on ${epair}b dnpipe 1" + + # check if the expected number of pings + # are dropped (84 - 96 responses). + # repeat up to 6 times if the initial + # checks fail + atf_check -s exit:0 -o match:'100 packets transmitted, (8[4-9]|9[0-6]) packets received' -r 6:10 ping -i 0.010 -c 100 192.0.2.2 +} + +pls_basic_cleanup() +{ + firewall_cleanup $1 +} + +pls_gilbert_head() +{ + atf_set descr 'dummynet Gilbert-Elliott packet loss model test' + atf_set require.user root +} + +pls_gilbert_body() +{ + fw=$1 + firewall_init $fw + dummynet_init $fw + + epair=$(vnet_mkepair) + vnet_mkjail alcatraz ${epair}b + + ifconfig ${epair}a 192.0.2.1/24 up + jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up + + firewall_config alcatraz ${fw} \ + "ipfw" \ + "ipfw add 65432 ip from any to any" \ + "pf" \ + "pass on ${epair}b" + + # Sanity check + atf_check -s exit:0 -o match:'100 packets transmitted, 100 packets received' ping -i .1 -c 100 192.0.2.2 + + jexec alcatraz dnctl pipe 1 config plr 0.01,0.1,0.8,0.2 + + firewall_config alcatraz ${fw} \ + "ipfw" \ + "ipfw add 1000 pipe 1 ip from 192.0.2.1 to 192.0.2.2" \ + "pf" \ + "pass on ${epair}b dnpipe 1" + + # check if the expected number of pings + # are dropped (70 - 85 responses). + # repeat up to 6 times if the initial + # checks fail + atf_check -s exit:0 -o match:'100 packets transmitted, (7[0-9]|8[0-5]) packets received' -r 6:10 ping -i 0.010 -c 100 192.0.2.2 +} + +pls_gilbert_cleanup() +{ + firewall_cleanup $1 +} + + + setup_tests \ interface_removal \ ipfw \ @@ -539,4 +635,10 @@ ipfw \ pf \ nat \ + pf \ + pls_basic \ + ipfw \ + pf \ + pls_gilbert \ + ipfw \ pf