ipfw: Introduce dnctl

Introduce a link to the ipfw command, dnctl, for dummynet configuration.
dnctl only handles dummynet configuration, and is part of the effort to
support dummynet in pf.

/sbin/ipfw continues to accept pipe, queue and sched commands, but these can
now also be issued via the new dnctl command.

Reviewed by:	donner
MFC after:	2 weeks
Sponsored by:	Rubicon Communications, LLC ("Netgate")
Differential Revision:	https://reviews.freebsd.org/D30465

(cherry picked from commit 0b95680e07)
This commit is contained in:
Kristof Provost 2021-05-25 16:54:32 +02:00
parent 8aeafec21a
commit 34055dacee
5 changed files with 189 additions and 116 deletions

View File

@ -4,6 +4,10 @@
PACKAGE=ipfw
PROG= ipfw
LINKS= ${BINDIR}/ipfw ${BINDIR}/dnctl
MLINKS= ipfw.8 dnctl.8
SRCS= ipfw2.c dummynet.c ipv6.c main.c nat.c tables.c
SRCS+= nat64clat.c nat64lsn.c nat64stl.c nptv6.c

View File

@ -1,11 +1,11 @@
.\"
.\" $FreeBSD$
.\"
.Dd June 4, 2021
.Dd June 14, 2021
.Dt IPFW 8
.Os
.Sh NAME
.Nm ipfw
.Nm ipfw , dnctl
.Nd User interface for firewall, traffic shaper, packet scheduler,
in-kernel NAT.
.Sh SYNOPSIS
@ -88,12 +88,12 @@ in-kernel NAT.
.Brq Ar name | all
.Cm flush
.Ss DUMMYNET CONFIGURATION (TRAFFIC SHAPER AND PACKET SCHEDULER)
.Nm
.Nm dnctl
.Brq Cm pipe | queue | sched
.Ar number
.Cm config
.Ar config-options
.Nm
.Nm dnctl
.Op Fl s Op Ar field
.Brq Cm pipe | queue | sched
.Brq Cm delete | list | show
@ -440,7 +440,7 @@ them on the local hostname) and the use of macros to centralize
frequently required arguments like IP addresses.
.Ss TRAFFIC SHAPER CONFIGURATION
The
.Nm
.Nm dnctl
.Cm pipe , queue
and
.Cm sched
@ -2650,11 +2650,11 @@ Bandwidth, measured in
A value of 0 (default) means unlimited bandwidth.
The unit must immediately follow the number, as in
.Pp
.Dl "ipfw pipe 1 config bw 300Kbit/s"
.Dl "dnctl pipe 1 config bw 300Kbit/s"
.Pp
If a device name is specified instead of a numeric value, as in
.Pp
.Dl "ipfw pipe 1 config bw tun0"
.Dl "dnctl pipe 1 config bw tun0"
.Pp
then the transmit clock is supplied by the specified device.
At the moment only the
@ -2731,7 +2731,7 @@ The file format is the following, with whitespace acting as
a separator and '#' indicating the beginning a comment:
.Bl -tag -width indent
.It Cm name Ar identifier
optional name (listed by "ipfw pipe show")
optional name (listed by "dnctl pipe show")
to identify the delay distribution;
.It Cm bw Ar value
the bandwidth used for the pipe.
@ -4356,15 +4356,15 @@ A similar effect can be achieved making use of
.Nm dummynet
pipes:
.Pp
.Dl "ipfw add pipe 10 ip from any to any"
.Dl "ipfw pipe 10 config plr 0.05"
.Dl "dnctl add pipe 10 ip from any to any"
.Dl "dnctl pipe 10 config plr 0.05"
.Pp
We can use pipes to artificially limit bandwidth, e.g.\& on a
machine acting as a router, if we want to limit traffic from
local clients on 192.168.2.0/24 we do:
.Pp
.Dl "ipfw add pipe 1 ip from 192.168.2.0/24 to any out"
.Dl "ipfw pipe 1 config bw 300Kbit/s queue 50KBytes"
.Dl "dnctl pipe 1 config bw 300Kbit/s queue 50KBytes"
.Pp
note that we use the
.Cm out
@ -4378,8 +4378,8 @@ limitations, the correct way is the following:
.Pp
.Dl "ipfw add pipe 1 ip from any to any out"
.Dl "ipfw add pipe 2 ip from any to any in"
.Dl "ipfw pipe 1 config bw 64Kbit/s queue 10Kbytes"
.Dl "ipfw pipe 2 config bw 64Kbit/s queue 10Kbytes"
.Dl "dnctl pipe 1 config bw 64Kbit/s queue 10Kbytes"
.Dl "dnctl pipe 2 config bw 64Kbit/s queue 10Kbytes"
.Pp
The above can be very useful, e.g.\& if you want to see how
your fancy Web page will look for a residential user who
@ -4394,7 +4394,7 @@ Should we want to verify network performance with the RED queue
management algorithm:
.Pp
.Dl "ipfw add pipe 1 ip from any to any"
.Dl "ipfw pipe 1 config bw 500Kbit/s queue 100 red 0.002/30/80/0.1"
.Dl "dnctl pipe 1 config bw 500Kbit/s queue 100 red 0.002/30/80/0.1"
.Pp
Another typical application of the traffic shaper is to
introduce some delay in the communication.
@ -4405,8 +4405,8 @@ bandwidth:
.Pp
.Dl "ipfw add pipe 1 ip from any to any out"
.Dl "ipfw add pipe 2 ip from any to any in"
.Dl "ipfw pipe 1 config delay 250ms bw 1Mbit/s"
.Dl "ipfw pipe 2 config delay 250ms bw 1Mbit/s"
.Dl "dnctl pipe 1 config delay 250ms bw 1Mbit/s"
.Dl "dnctl pipe 2 config delay 250ms bw 1Mbit/s"
.Pp
Per-flow queueing can be useful for a variety of purposes.
A very simple one is counting traffic:
@ -4414,7 +4414,7 @@ A very simple one is counting traffic:
.Dl "ipfw add pipe 1 tcp from any to any"
.Dl "ipfw add pipe 1 udp from any to any"
.Dl "ipfw add pipe 1 ip from any to any"
.Dl "ipfw pipe 1 config mask all"
.Dl "dnctl pipe 1 config mask all"
.Pp
The above set of rules will create queues (and collect
statistics) for all traffic.
@ -4432,8 +4432,8 @@ on a net with per-host limits, rather than per-network limits:
.Pp
.Dl "ipfw add pipe 1 ip from 192.168.2.0/24 to any out"
.Dl "ipfw add pipe 2 ip from any to 192.168.2.0/24 in"
.Dl "ipfw pipe 1 config mask src-ip 0x000000ff bw 200Kbit/s queue 20Kbytes"
.Dl "ipfw pipe 2 config mask dst-ip 0x000000ff bw 200Kbit/s queue 20Kbytes"
.Dl "dnctl pipe 1 config mask src-ip 0x000000ff bw 200Kbit/s queue 20Kbytes"
.Dl "dnctl pipe 2 config mask dst-ip 0x000000ff bw 200Kbit/s queue 20Kbytes"
.Ss LOOKUP TABLES
In the following example, we need to create several traffic bandwidth
classes and we need different hosts/networks to fall into different classes.
@ -4443,8 +4443,8 @@ For each subnet/host we set the argument equal to the number of the pipe
that it should use.
Then we classify traffic using a single rule:
.Pp
.Dl "ipfw pipe 1 config bw 1000Kbyte/s"
.Dl "ipfw pipe 4 config bw 4000Kbyte/s"
.Dl "dnctl pipe 1 config bw 1000Kbyte/s"
.Dl "dnctl pipe 4 config bw 4000Kbyte/s"
.Dl "..."
.Dl "ipfw table T1 create type addr"
.Dl "ipfw table T1 add 192.168.2.0/24 1"
@ -4626,7 +4626,7 @@ with
AQM using default configuration for traffic from 192.168.0.0/24 and 1Mbits/s
rate limit, we do:
.Pp
.Dl "ipfw pipe 1 config bw 1mbits/s codel"
.Dl "dnctl pipe 1 config bw 1mbits/s codel"
.Dl "ipfw add 100 pipe 1 ip from 192.168.0.0/24 to any"
.Pp
To configure a
@ -4636,8 +4636,8 @@ with
AQM using different configurations parameters for traffic from
192.168.0.0/24 and 1Mbits/s rate limit, we do:
.Pp
.Dl "ipfw pipe 1 config bw 1mbits/s"
.Dl "ipfw queue 1 config pipe 1 codel target 8ms interval 160ms ecn"
.Dl "dnctl pipe 1 config bw 1mbits/s"
.Dl "dnctl queue 1 config pipe 1 codel target 8ms interval 160ms ecn"
.Dl "ipfw add 100 queue 1 ip from 192.168.0.0/24 to any"
.Pp
To configure a
@ -4647,7 +4647,7 @@ with
AQM using default configuration for traffic from 192.168.0.0/24 and 1Mbits/s
rate limit, we do:
.Pp
.Dl "ipfw pipe 1 config bw 1mbits/s pie"
.Dl "dnctl pipe 1 config bw 1mbits/s pie"
.Dl "ipfw add 100 pipe 1 ip from 192.168.0.0/24 to any"
.Pp
To configure a
@ -4657,8 +4657,8 @@ with
AQM using different configuration parameters for traffic from
192.168.0.0/24 and 1Mbits/s rate limit, we do:
.Pp
.Dl "ipfw pipe 1 config bw 1mbits/s"
.Dl "ipfw queue 1 config pipe 1 pie target 20ms tupdate 30ms ecn"
.Dl "dnctl pipe 1 config bw 1mbits/s"
.Dl "dnctl queue 1 config pipe 1 pie target 20ms tupdate 30ms ecn"
.Dl "ipfw add 100 queue 1 ip from 192.168.0.0/24 to any"
.Pp
.Cm fq_codel
@ -4673,9 +4673,9 @@ To configure
scheduler using different configurations parameters for traffic from
192.168.0.0/24 and 1Mbits/s rate limit, we do:
.Pp
.Dl "ipfw pipe 1 config bw 1mbits/s"
.Dl "ipfw sched 1 config pipe 1 type fq_codel"
.Dl "ipfw queue 1 config sched 1"
.Dl "dnctl pipe 1 config bw 1mbits/s"
.Dl "dnctl sched 1 config pipe 1 type fq_codel"
.Dl "dnctl queue 1 config sched 1"
.Dl "ipfw add 100 queue 1 ip from 192.168.0.0/24 to any"
.Pp
To change
@ -4686,7 +4686,7 @@ such as disable ECN and change the
.Ar target
to 10ms, we do:
.Pp
.Dl "ipfw sched 1 config pipe 1 type fq_codel target 10ms noecn"
.Dl "dnctl sched 1 config pipe 1 type fq_codel target 10ms noecn"
.Pp
Similar to
.Cm fq_codel ,
@ -4695,9 +4695,9 @@ to configure
scheduler using different configurations parameters for traffic from
192.168.0.0/24 and 1Mbits/s rate limit, we do:
.Pp
.Dl "ipfw pipe 1 config bw 1mbits/s"
.Dl "ipfw sched 1 config pipe 1 type fq_pie"
.Dl "ipfw queue 1 config sched 1"
.Dl "dnctl pipe 1 config bw 1mbits/s"
.Dl "dnctl sched 1 config pipe 1 type fq_pie"
.Dl "dnctl queue 1 config sched 1"
.Dl "ipfw add 100 queue 1 ip from 192.168.0.0/24 to any"
.Pp
The configurations of

View File

@ -411,6 +411,12 @@ static void object_sort_ctlv(ipfw_obj_ctlv *ctlv);
static char *object_search_ctlv(ipfw_obj_ctlv *ctlv, uint16_t idx,
uint16_t type);
int
is_ipfw(void)
{
return (g_co.prog == cmdline_prog_ipfw);
}
/*
* Simple string buffer API.
* Used to simplify buffer passing between function and for

View File

@ -20,6 +20,11 @@
* $FreeBSD$
*/
enum cmdline_prog {
cmdline_prog_ipfw,
cmdline_prog_dnctl
};
/*
* Options that can be set on the command line.
* When reading commands from a file, a subset of the options can also
@ -54,8 +59,11 @@ struct cmdline_opts {
uint32_t use_set; /* work with specified set number */
/* 0 means all sets, otherwise apply to set use_set - 1 */
enum cmdline_prog prog; /* Are we ipfw or dnctl? */
};
int is_ipfw(void);
enum {
TIMESTAMP_NONE = 0,
TIMESTAMP_STRING,

View File

@ -36,7 +36,8 @@
static void
help(void)
{
fprintf(stderr,
if (is_ipfw()) {
fprintf(stderr,
"ipfw syntax summary (but please do read the ipfw(8) manpage):\n\n"
"\tipfw [-abcdefhnNqStTv] <command>\n\n"
"where <command> is one of the following:\n\n"
@ -76,6 +77,16 @@ help(void)
" setup | {tcpack|tcpseq|tcpwin} NN | tcpflags SPEC | tcpoptions SPEC |\n"
" tcpdatalen LIST | verrevpath | versrcreach | antispoof\n"
);
} else {
fprintf(stderr,
"dnctl syntax summary (but please do read the dnctl(8) manpage):\n\n"
"\tdnctl [-hnsv] <command>\n\n"
"where <command> is one of the following:\n\n"
"[pipe|queue|sched] N config PIPE-BODY\n"
"[pipe|queue|sched] {delete|list|show} [N{,N}]\n"
"\n"
);
}
exit(0);
}
@ -231,7 +242,8 @@ ipfw_main(int oldac, char **oldav)
g_co.do_force = !isatty(STDIN_FILENO);
#ifdef EMULATE_SYSCTL /* sysctl emulation */
if ( ac >= 2 && !strcmp(av[1], "sysctl")) {
if (is_ipfw() && ac >= 2 &&
!strcmp(av[1], "sysctl")) {
char *s;
int i;
@ -263,87 +275,115 @@ ipfw_main(int oldac, char **oldav)
save_av = av;
optind = optreset = 1; /* restart getopt() */
while ((ch = getopt(ac, av, "abcdDefhinNp:qs:STtv")) != -1)
switch (ch) {
case 'a':
do_acct = 1;
break;
if (is_ipfw()) {
while ((ch = getopt(ac, av, "abcdDefhinNp:qs:STtv")) != -1)
switch (ch) {
case 'a':
do_acct = 1;
break;
case 'b':
g_co.comment_only = 1;
g_co.do_compact = 1;
break;
case 'b':
g_co.comment_only = 1;
g_co.do_compact = 1;
break;
case 'c':
g_co.do_compact = 1;
break;
case 'c':
g_co.do_compact = 1;
break;
case 'd':
g_co.do_dynamic = 1;
break;
case 'd':
g_co.do_dynamic = 1;
break;
case 'D':
g_co.do_dynamic = 2;
break;
case 'D':
g_co.do_dynamic = 2;
break;
case 'e':
/* nop for compatibility */
break;
case 'e':
/* nop for compatibility */
break;
case 'f':
g_co.do_force = 1;
break;
case 'f':
g_co.do_force = 1;
break;
case 'h': /* help */
free(save_av);
help();
break; /* NOTREACHED */
case 'h': /* help */
free(save_av);
help();
break; /* NOTREACHED */
case 'i':
g_co.do_value_as_ip = 1;
break;
case 'i':
g_co.do_value_as_ip = 1;
break;
case 'n':
g_co.test_only = 1;
break;
case 'n':
g_co.test_only = 1;
break;
case 'N':
g_co.do_resolv = 1;
break;
case 'N':
g_co.do_resolv = 1;
break;
case 'p':
errx(EX_USAGE, "An absolute pathname must be used "
"with -p option.");
/* NOTREACHED */
case 'p':
errx(EX_USAGE, "An absolute pathname must be used "
"with -p option.");
/* NOTREACHED */
case 'q':
g_co.do_quiet = 1;
break;
case 'q':
g_co.do_quiet = 1;
break;
case 's': /* sort */
g_co.do_sort = atoi(optarg);
break;
case 's': /* sort */
g_co.do_sort = atoi(optarg);
break;
case 'S':
g_co.show_sets = 1;
break;
case 'S':
g_co.show_sets = 1;
break;
case 't':
g_co.do_time = TIMESTAMP_STRING;
break;
case 't':
g_co.do_time = TIMESTAMP_STRING;
break;
case 'T':
g_co.do_time = TIMESTAMP_NUMERIC;
break;
case 'T':
g_co.do_time = TIMESTAMP_NUMERIC;
break;
case 'v': /* verbose */
g_co.verbose = 1;
break;
case 'v': /* verbose */
g_co.verbose = 1;
break;
default:
free(save_av);
return 1;
}
default:
free(save_av);
return 1;
}
} else {
while ((ch = getopt(ac, av, "hns:v")) != -1)
switch (ch) {
case 'h': /* help */
free(save_av);
help();
break; /* NOTREACHED */
case 'n':
g_co.test_only = 1;
break;
case 's': /* sort */
g_co.do_sort = atoi(optarg);
break;
case 'v': /* verbose */
g_co.verbose = 1;
break;
default:
free(save_av);
return 1;
}
}
ac -= optind;
av += optind;
@ -367,7 +407,7 @@ ipfw_main(int oldac, char **oldav)
g_co.do_nat = 0;
g_co.do_pipe = 0;
g_co.use_set = 0;
if (!strncmp(*av, "nat", strlen(*av)))
if (is_ipfw() && !strncmp(*av, "nat", strlen(*av)))
g_co.do_nat = 1;
else if (!strncmp(*av, "pipe", strlen(*av)))
g_co.do_pipe = 1;
@ -377,7 +417,7 @@ ipfw_main(int oldac, char **oldav)
g_co.do_pipe = 2;
else if (_substrcmp(*av, "sched") == 0)
g_co.do_pipe = 3;
else if (!strncmp(*av, "set", strlen(*av))) {
else if (is_ipfw() && !strncmp(*av, "set", strlen(*av))) {
if (ac > 1 && isdigit(av[1][0])) {
g_co.use_set = strtonum(av[1], 0, resvd_set_number,
&errstr);
@ -406,8 +446,12 @@ ipfw_main(int oldac, char **oldav)
av[1] = p;
}
if (! is_ipfw() && g_co.do_pipe == 0) {
help();
}
if (g_co.use_set == 0) {
if (_substrcmp(*av, "add") == 0)
if (is_ipfw() && _substrcmp(*av, "add") == 0)
ipfw_add(av);
else if (g_co.do_nat && _substrcmp(*av, "show") == 0)
ipfw_show_nat(ac, av);
@ -415,13 +459,13 @@ ipfw_main(int oldac, char **oldav)
ipfw_config_pipe(ac, av);
else if (g_co.do_nat && _substrcmp(*av, "config") == 0)
ipfw_config_nat(ac, av);
else if (_substrcmp(*av, "set") == 0)
else if (is_ipfw() && _substrcmp(*av, "set") == 0)
ipfw_sets_handler(av);
else if (_substrcmp(*av, "table") == 0)
else if (is_ipfw() && _substrcmp(*av, "table") == 0)
ipfw_table_handler(ac, av);
else if (_substrcmp(*av, "enable") == 0)
else if (is_ipfw() && _substrcmp(*av, "enable") == 0)
ipfw_sysctl_handler(av, 1);
else if (_substrcmp(*av, "disable") == 0)
else if (is_ipfw() && _substrcmp(*av, "disable") == 0)
ipfw_sysctl_handler(av, 0);
else
try_next = 1;
@ -430,28 +474,28 @@ ipfw_main(int oldac, char **oldav)
if (g_co.use_set || try_next) {
if (_substrcmp(*av, "delete") == 0)
ipfw_delete(av);
else if (!strncmp(*av, "nat64clat", strlen(*av)))
else if (is_ipfw() && !strncmp(*av, "nat64clat", strlen(*av)))
ipfw_nat64clat_handler(ac, av);
else if (!strncmp(*av, "nat64stl", strlen(*av)))
else if (is_ipfw() && !strncmp(*av, "nat64stl", strlen(*av)))
ipfw_nat64stl_handler(ac, av);
else if (!strncmp(*av, "nat64lsn", strlen(*av)))
else if (is_ipfw() && !strncmp(*av, "nat64lsn", strlen(*av)))
ipfw_nat64lsn_handler(ac, av);
else if (!strncmp(*av, "nptv6", strlen(*av)))
else if (is_ipfw() && !strncmp(*av, "nptv6", strlen(*av)))
ipfw_nptv6_handler(ac, av);
else if (_substrcmp(*av, "flush") == 0)
ipfw_flush(g_co.do_force);
else if (_substrcmp(*av, "zero") == 0)
else if (is_ipfw() && _substrcmp(*av, "zero") == 0)
ipfw_zero(ac, av, 0 /* IP_FW_ZERO */);
else if (_substrcmp(*av, "resetlog") == 0)
else if (is_ipfw() && _substrcmp(*av, "resetlog") == 0)
ipfw_zero(ac, av, 1 /* IP_FW_RESETLOG */);
else if (_substrcmp(*av, "print") == 0 ||
_substrcmp(*av, "list") == 0)
ipfw_list(ac, av, do_acct);
else if (_substrcmp(*av, "show") == 0)
ipfw_list(ac, av, 1 /* show counters */);
else if (_substrcmp(*av, "table") == 0)
else if (is_ipfw() && _substrcmp(*av, "table") == 0)
ipfw_table_handler(ac, av);
else if (_substrcmp(*av, "internal") == 0)
else if (is_ipfw() && _substrcmp(*av, "internal") == 0)
ipfw_internal_handler(ac, av);
else
errx(EX_USAGE, "bad command `%s'", *av);
@ -620,12 +664,22 @@ main(int ac, char *av[])
}
}
#endif
if (strcmp(av[0], "dnctl") == 0)
g_co.prog = cmdline_prog_dnctl;
else
g_co.prog = cmdline_prog_ipfw;
/*
* If the last argument is an absolute pathname, interpret it
* as a file to be preprocessed.
*/
if (ac > 1 && av[ac - 1][0] == '/') {
if (! is_ipfw())
errx(EX_USAGE, "usage: dnctl [options]\n"
"do \"dnctl -h\" for details");
if (access(av[ac - 1], R_OK) == 0)
ipfw_readfile(ac, av);
else
@ -633,8 +687,9 @@ main(int ac, char *av[])
} else {
if (ipfw_main(ac, av)) {
errx(EX_USAGE,
"usage: ipfw [options]\n"
"do \"ipfw -h\" or \"man ipfw\" for details");
"usage: %s [options]\n"
"do \"%s -h\" or \"man %s\" for details", av[0],
av[0], av[0]);
}
}
return EX_OK;