mirror of
https://github.com/freebsd/freebsd-src
synced 2024-10-07 00:50:50 +00:00
dummynet: add simple gilbert-elliott channel model
Have a simple Gilbert-Elliott channel model in dummynet to mimick correlated loss behavior of realistic environments. This allows simpler testing of burst-loss environments. Reviewed By: tuexen, kp, pauamma_gundo.com, #manpages Sponsored by: NetApp, Inc. Differential Revision: https://reviews.freebsd.org/D42980
This commit is contained in:
parent
4fb5eda649
commit
31cf66d755
|
@ -471,7 +471,7 @@ print_flowset_parms(struct dn_fs *fs, char *prefix)
|
||||||
{
|
{
|
||||||
int l;
|
int l;
|
||||||
char qs[30];
|
char qs[30];
|
||||||
char plr[30];
|
char plr[40];
|
||||||
char red[200]; /* Display RED parameters */
|
char red[200]; /* Display RED parameters */
|
||||||
|
|
||||||
l = fs->qsize;
|
l = fs->qsize;
|
||||||
|
@ -482,9 +482,17 @@ print_flowset_parms(struct dn_fs *fs, char *prefix)
|
||||||
sprintf(qs, "%d B", l);
|
sprintf(qs, "%d B", l);
|
||||||
} else
|
} else
|
||||||
sprintf(qs, "%3d sl.", l);
|
sprintf(qs, "%3d sl.", l);
|
||||||
if (fs->plr)
|
if (fs->plr[0] || fs->plr[1]) {
|
||||||
sprintf(plr, "plr %f", 1.0 * fs->plr / (double)(0x7fffffff));
|
if (fs->plr[1] == 0)
|
||||||
else
|
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';
|
plr[0] = '\0';
|
||||||
|
|
||||||
if (fs->flags & DN_IS_RED) { /* RED parameters */
|
if (fs->flags & DN_IS_RED) { /* RED parameters */
|
||||||
|
@ -1408,13 +1416,27 @@ ipfw_config_pipe(int ac, char **av)
|
||||||
|
|
||||||
case TOK_PLR:
|
case TOK_PLR:
|
||||||
NEED(fs, "plr is only for pipes");
|
NEED(fs, "plr is only for pipes");
|
||||||
NEED1("plr needs argument 0..1\n");
|
NEED1("plr needs one or four arguments 0..1\n");
|
||||||
d = strtod(av[0], NULL);
|
if ((end = strsep(&av[0], ","))) {
|
||||||
if (d > 1)
|
d = strtod(end, NULL);
|
||||||
d = 1;
|
d = (d < 0) ? 0 : (d <= 1) ? d : 1;
|
||||||
else if (d < 0)
|
fs->plr[0] = (int)(d*0x7fffffff);
|
||||||
d = 0;
|
}
|
||||||
fs->plr = (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++;
|
ac--; av++;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
.\"
|
.\"
|
||||||
.Dd September 28, 2023
|
.Dd December 17, 2023
|
||||||
.Dt IPFW 8
|
.Dt IPFW 8
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
|
@ -3039,12 +3039,47 @@ needed for some experimental setups where you want to simulate
|
||||||
loss or congestion at a remote router.
|
loss or congestion at a remote router.
|
||||||
.Pp
|
.Pp
|
||||||
.It Cm plr Ar packet-loss-rate
|
.It Cm plr Ar packet-loss-rate
|
||||||
|
.It Cm plr Ar K,p,H,r
|
||||||
Packet loss rate.
|
Packet loss rate.
|
||||||
Argument
|
Argument
|
||||||
.Ar packet-loss-rate
|
.Ar packet-loss-rate
|
||||||
is a floating-point number between 0 and 1, with 0 meaning no
|
is a floating-point number between 0 and 1, with 0 meaning no
|
||||||
loss, 1 meaning 100% loss.
|
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
|
.Pp
|
||||||
.It Cm queue Brq Ar slots | size Ns Cm Kbytes
|
.It Cm queue Brq Ar slots | size Ns Cm Kbytes
|
||||||
Queue size, in
|
Queue size, in
|
||||||
|
|
|
@ -145,7 +145,7 @@ struct dn_fs {
|
||||||
uint32_t fs_nr; /* the flowset number */
|
uint32_t fs_nr; /* the flowset number */
|
||||||
uint32_t flags; /* userland flags */
|
uint32_t flags; /* userland flags */
|
||||||
int qsize; /* queue size in slots or bytes */
|
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 */
|
uint32_t buckets; /* buckets used for the queue hash table */
|
||||||
|
|
||||||
struct ipfw_flow_id flow_mask;
|
struct ipfw_flow_id flow_mask;
|
||||||
|
@ -168,6 +168,7 @@ struct dn_fs {
|
||||||
int min_th ; /* minimum threshold for queue (scaled) */
|
int min_th ; /* minimum threshold for queue (scaled) */
|
||||||
int max_p ; /* maximum value for p_b (scaled) */
|
int max_p ; /* maximum value for p_b (scaled) */
|
||||||
|
|
||||||
|
int32_t plr[4]; /* PLR, pkt loss rate (2^31-1 means 100%) */
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -77,35 +77,35 @@ struct dn_heap7 {
|
||||||
|
|
||||||
/* Common to 7.2 and 8 */
|
/* Common to 7.2 and 8 */
|
||||||
struct dn_flow_set {
|
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;
|
u_short flags_fs;
|
||||||
#define DNOLD_HAVE_FLOW_MASK 0x0001
|
#define DNOLD_HAVE_FLOW_MASK 0x0001
|
||||||
#define DNOLD_IS_RED 0x0002
|
#define DNOLD_IS_RED 0x0002
|
||||||
#define DNOLD_IS_GENTLE_RED 0x0004
|
#define DNOLD_IS_GENTLE_RED 0x0004
|
||||||
#define DNOLD_QSIZE_IS_BYTES 0x0008 /* queue size is measured in bytes */
|
#define DNOLD_QSIZE_IS_BYTES 0x0008 /* queue size is measured in bytes */
|
||||||
#define DNOLD_NOERROR 0x0010 /* do not report ENOBUFS on drops */
|
#define DNOLD_NOERROR 0x0010 /* do not report ENOBUFS on drops */
|
||||||
#define DNOLD_HAS_PROFILE 0x0020 /* the pipe has a delay profile. */
|
#define DNOLD_HAS_PROFILE 0x0020 /* the pipe has a delay profile. */
|
||||||
#define DNOLD_IS_PIPE 0x4000
|
#define DNOLD_IS_PIPE 0x4000
|
||||||
#define DNOLD_IS_QUEUE 0x8000
|
#define DNOLD_IS_QUEUE 0x8000
|
||||||
|
|
||||||
struct dn_pipe7 *pipe ; /* pointer to parent pipe */
|
struct dn_pipe7 *pipe ; /* pointer to parent pipe */
|
||||||
u_short parent_nr ; /* parent pipe#, 0 if local to a pipe */
|
u_short parent_nr ; /* parent pipe#, 0 if local to a pipe */
|
||||||
|
|
||||||
int weight ; /* WFQ queue weight */
|
int weight ; /* WFQ queue weight */
|
||||||
int qsize ; /* queue size in slots or bytes */
|
int qsize ; /* queue size in slots or bytes */
|
||||||
int plr ; /* pkt loss rate (2^31-1 means 100%) */
|
int plr[4] ; /* pkt loss rate (2^31-1 means 100%) */
|
||||||
|
|
||||||
struct ipfw_flow_id flow_mask ;
|
struct ipfw_flow_id flow_mask ;
|
||||||
|
|
||||||
/* hash table of queues onto this flow_set */
|
/* hash table of queues onto this flow_set */
|
||||||
int rq_size ; /* number of slots */
|
int rq_size ; /* number of slots */
|
||||||
int rq_elements ; /* active elements */
|
int rq_elements ; /* active elements */
|
||||||
struct dn_flow_queue7 **rq; /* array of rq_size entries */
|
struct dn_flow_queue7 **rq ; /* array of rq_size entries */
|
||||||
|
|
||||||
u_int32_t last_expired ; /* do not expire too frequently */
|
u_int32_t last_expired ; /* do not expire too frequently */
|
||||||
int backlogged ; /* #active queues for this flowset */
|
int backlogged ; /* #active queues for this flowset */
|
||||||
|
|
||||||
/* RED parameters */
|
/* RED parameters */
|
||||||
#define SCALE_RED 16
|
#define SCALE_RED 16
|
||||||
|
@ -420,7 +420,10 @@ dn_compat_config_queue(struct dn_fs *fs, void* v)
|
||||||
fs->flow_mask = f->flow_mask;
|
fs->flow_mask = f->flow_mask;
|
||||||
fs->buckets = f->rq_size;
|
fs->buckets = f->rq_size;
|
||||||
fs->qsize = f->qsize;
|
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->par[0] = f->weight;
|
||||||
fs->flags = convertflags2new(f->flags_fs);
|
fs->flags = convertflags2new(f->flags_fs);
|
||||||
if (fs->flags & DN_IS_GENTLE_RED || fs->flags & DN_IS_RED) {
|
if (fs->flags & DN_IS_GENTLE_RED || fs->flags & DN_IS_RED) {
|
||||||
|
@ -645,7 +648,10 @@ dn_c_copy_pipe(struct dn_schk *s, struct copy_args *a, int nq)
|
||||||
|
|
||||||
fs->parent_nr = l->link_nr - DN_MAX_ID;
|
fs->parent_nr = l->link_nr - DN_MAX_ID;
|
||||||
fs->qsize = f->fs.qsize;
|
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->w_q = f->fs.w_q;
|
||||||
fs->max_th = f->max_th;
|
fs->max_th = f->max_th;
|
||||||
fs->min_th = f->min_th;
|
fs->min_th = f->min_th;
|
||||||
|
@ -698,7 +704,10 @@ dn_c_copy_fs(struct dn_fsk *f, struct copy_args *a, int nq)
|
||||||
fs->next.sle_next = (struct dn_flow_set *)DN_IS_QUEUE;
|
fs->next.sle_next = (struct dn_flow_set *)DN_IS_QUEUE;
|
||||||
fs->fs_nr = f->fs.fs_nr;
|
fs->fs_nr = f->fs.fs_nr;
|
||||||
fs->qsize = f->fs.qsize;
|
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->w_q = f->fs.w_q;
|
||||||
fs->max_th = f->max_th;
|
fs->max_th = f->max_th;
|
||||||
fs->min_th = f->min_th;
|
fs->min_th = f->min_th;
|
||||||
|
|
|
@ -497,8 +497,28 @@ dn_enqueue(struct dn_queue *q, struct mbuf* m, int drop)
|
||||||
ni->tot_pkts++;
|
ni->tot_pkts++;
|
||||||
if (drop)
|
if (drop)
|
||||||
goto drop;
|
goto drop;
|
||||||
if (f->plr && random() < f->plr)
|
if (f->plr[0] || f->plr[1]) {
|
||||||
goto drop;
|
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)
|
if (m->m_pkthdr.rcvif != NULL)
|
||||||
m_rcvif_serialize(m);
|
m_rcvif_serialize(m);
|
||||||
#ifdef NEW_AQM
|
#ifdef NEW_AQM
|
||||||
|
|
|
@ -392,6 +392,15 @@ enum {
|
||||||
PROTO_IFB = 0x0c, /* layer2 + ifbridge */
|
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;
|
//extern struct dn_parms V_dn_cfg;
|
||||||
VNET_DECLARE(struct dn_parms, dn_cfg);
|
VNET_DECLARE(struct dn_parms, dn_cfg);
|
||||||
#define V_dn_cfg VNET(dn_cfg)
|
#define V_dn_cfg VNET(dn_cfg)
|
||||||
|
|
|
@ -517,6 +517,102 @@ nat_cleanup()
|
||||||
firewall_cleanup $1
|
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 \
|
setup_tests \
|
||||||
interface_removal \
|
interface_removal \
|
||||||
ipfw \
|
ipfw \
|
||||||
|
@ -539,4 +635,10 @@ setup_tests \
|
||||||
ipfw \
|
ipfw \
|
||||||
pf \
|
pf \
|
||||||
nat \
|
nat \
|
||||||
|
pf \
|
||||||
|
pls_basic \
|
||||||
|
ipfw \
|
||||||
|
pf \
|
||||||
|
pls_gilbert \
|
||||||
|
ipfw \
|
||||||
pf
|
pf
|
||||||
|
|
Loading…
Reference in a new issue