Add to IPFW the ability to do ALTQ classification/tagging.

This commit is contained in:
Brian Feldman 2004-10-03 00:17:46 +00:00
parent 8ceb3dcb60
commit 974dfe3084
5 changed files with 312 additions and 33 deletions

View File

@ -4,5 +4,6 @@ PROG= ipfw
SRCS= ipfw2.c
WARNS?= 0
MAN= ipfw.8
CFLAGS+= -I${.CURDIR}/../../sys/contrib/pf
.include <bsd.prog.mk>

View File

@ -26,10 +26,10 @@
.Op Ar number ...
.Nm
.Cm enable
.Brq Cm firewall | one_pass | debug | verbose | dyn_keepalive
.Brq Cm firewall | altq | one_pass | debug | verbose | dyn_keepalive
.Nm
.Cm disable
.Brq Cm firewall | one_pass | debug | verbose | dyn_keepalive
.Brq Cm firewall | altq | one_pass | debug | verbose | dyn_keepalive
.Pp
.Nm
.Cm set Oo Cm disable Ar number ... Oc Op Cm enable Ar number ...
@ -438,6 +438,7 @@ rules is the following:
.br
.Ar " " action
.Op Cm log Op Cm logamount Ar number
.Op Cm altq Ar queue
.Ar body
.Ed
.Pp
@ -469,6 +470,8 @@ window
for ICMP packets
.It User/group ID
When the packet can be associated with a local socket.
.It Divert status
Whether a packet came from a divert socket (e.g. natd).
.El
.Pp
Note that some of the above information, e.g.\& source MAC or IP addresses and
@ -560,6 +563,41 @@ command.
Note: logging is done after all other packet matching conditions
have been successfully verified, and before performing the final
action (accept, deny, etc.) on the packet.
.It Cm altq Ar queue
When a packet matches a rule with the
.Cm altq
keyword, the ALTQ identifier for the given
.Ar queue
(see
.Xr pf.conf 5 )
will be attached.
Note that this ALTQ tag is only meaningful for packets going "out" of IPFW,
and not being rejected or going to divert sockets.
Note that if there is insufficient memory at the time the packet is
processed, it will not be tagged, so it is wise to make your ALTQ
"default" queue policy account for this.
If multiple
.Cm altq
rules match a single packet, subsequent tags are ignored by ALTQ.
.Pp
You must run
.Xr pfctl 8
to set up the queues before IPFW will be able to look them up by name,
and if the ALTQ disciplines are rearranged, the rules in containing the
queue identifiers in the kernel will likely have gone stale and need
to be reloaded.
Stale queue identifiers will probably misclassify
.Pp
All system ALTQ processing can be turned on or off via
.Nm
.Cm enable Ar altq
and
.Nm
.Cm disable Ar altq .
The usage of
.Em net.inet.ip.fw.one_pass
is irrelevant to ALTQ traffic shaping, as the actual rule action is followed
always after adding an ALTQ tag.
.El
.Ss RULE ACTIONS
A rule can be associated with one of the following actions, which
@ -2275,6 +2313,7 @@ the sleep terminates thus restoring the previous situation.
.Xr ip 4 ,
.Xr ipfirewall 4 ,
.Xr protocols 5 ,
.Xr pf.conf 5 ,
.Xr services 5 ,
.Xr init 8 ,
.Xr kldload 8 ,

View File

@ -27,6 +27,7 @@
#include <sys/sysctl.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <sys/queue.h>
#include <ctype.h>
#include <err.h>
@ -43,8 +44,11 @@
#include <timeconv.h> /* XXX do we need this ? */
#include <unistd.h>
#include <sysexits.h>
#include <unistd.h>
#include <fcntl.h>
#include <net/if.h>
#include <net/pfvar.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
@ -202,6 +206,9 @@ enum tokens {
TOK_UNREACH,
TOK_CHECKSTATE,
TOK_ALTQ,
TOK_LOG,
TOK_UID,
TOK_GID,
TOK_JAIL,
@ -302,6 +309,12 @@ struct _s_x rule_actions[] = {
{ NULL, 0 } /* terminator */
};
struct _s_x rule_action_params[] = {
{ "altq", TOK_ALTQ },
{ "log", TOK_LOG },
{ NULL, 0 } /* terminator */
};
struct _s_x rule_options[] = {
{ "uid", TOK_UID },
{ "gid", TOK_GID },
@ -562,6 +575,107 @@ strtoport(char *s, char **end, int base, int proto)
return 0; /* not found */
}
/*
* Map between current altq queue id numbers and names.
*/
static int altq_fetched = 0;
static TAILQ_HEAD(, pf_altq) altq_entries =
TAILQ_HEAD_INITIALIZER(altq_entries);
static void
altq_set_enabled(int enabled)
{
int pffd;
pffd = open("/dev/pf", O_RDWR);
if (pffd == -1)
err(EX_UNAVAILABLE,
"altq support opening pf(4) control device");
if (enabled) {
if (ioctl(pffd, DIOCSTARTALTQ) != 0 && errno != EEXIST)
err(EX_UNAVAILABLE, "enabling altq");
} else {
if (ioctl(pffd, DIOCSTOPALTQ) != 0 && errno != ENOENT)
err(EX_UNAVAILABLE, "disabling altq");
}
close(pffd);
}
static void
altq_fetch()
{
struct pfioc_altq pfioc;
struct pf_altq *altq;
int pffd, mnr;
if (altq_fetched)
return;
altq_fetched = 1;
pffd = open("/dev/pf", O_RDONLY);
if (pffd == -1) {
warn("altq support opening pf(4) control device");
return;
}
bzero(&pfioc, sizeof(pfioc));
if (ioctl(pffd, DIOCGETALTQS, &pfioc) != 0) {
warn("altq support getting queue list");
close(pffd);
return;
}
mnr = pfioc.nr;
for (pfioc.nr = 0; pfioc.nr < mnr; pfioc.nr++) {
if (ioctl(pffd, DIOCGETALTQ, &pfioc) != 0) {
if (errno == EBUSY)
break;
warn("altq support getting queue list");
close(pffd);
return;
}
if (pfioc.altq.qid == 0)
continue;
altq = malloc(sizeof(*altq));
if (altq == NULL)
err(EX_OSERR, "malloc");
*altq = pfioc.altq;
TAILQ_INSERT_TAIL(&altq_entries, altq, entries);
}
close(pffd);
}
static u_int32_t
altq_name_to_qid(const char *name)
{
struct pf_altq *altq;
altq_fetch();
TAILQ_FOREACH(altq, &altq_entries, entries)
if (strcmp(name, altq->qname) == 0)
break;
if (altq == NULL)
errx(EX_DATAERR, "altq has no queue named `%s'", name);
return altq->qid;
}
static const char *
altq_qid_to_name(u_int32_t qid)
{
struct pf_altq *altq;
altq_fetch();
TAILQ_FOREACH(altq, &altq_entries, entries)
if (qid == altq->qid)
break;
if (altq == NULL)
return NULL;
return altq->qname;
}
static void
fill_altq_qid(u_int32_t *qid, const char *av)
{
*qid = altq_name_to_qid(av);
}
/*
* Fill the body of the command with the list of port ranges.
*/
@ -908,6 +1022,7 @@ show_ipfw(struct ip_fw *rule, int pcwidth, int bcwidth)
int proto = 0; /* default */
int flags = 0; /* prerequisites */
ipfw_insn_log *logptr = NULL; /* set if we find an O_LOG */
ipfw_insn_altq *altqptr = NULL; /* set if we find an O_ALTQ */
int or_block = 0; /* we are in an or block */
uint32_t set_disable;
@ -1033,8 +1148,12 @@ show_ipfw(struct ip_fw *rule, int pcwidth, int bcwidth)
logptr = (ipfw_insn_log *)cmd;
break;
case O_ALTQ: /* O_ALTQ is printed after O_LOG */
altqptr = (ipfw_insn_altq *)cmd;
break;
default:
printf("** unrecognized action %d len %d",
printf("** unrecognized action %d len %d ",
cmd->opcode, cmd->len);
}
}
@ -1044,6 +1163,15 @@ show_ipfw(struct ip_fw *rule, int pcwidth, int bcwidth)
else
printf(" log");
}
if (altqptr) {
const char *qname;
qname = altq_qid_to_name(altqptr->qid);
if (qname == NULL)
printf(" altq ?<%u>", altqptr->qid);
else
printf(" altq %s", qname);
}
/*
* then print the body.
@ -1174,6 +1302,23 @@ show_ipfw(struct ip_fw *rule, int pcwidth, int bcwidth)
printf(cmd->len & F_NOT ? " out" : " in");
break;
case O_DIVERTED:
switch (cmd->arg1) {
case 3:
printf(" diverted");
break;
case 1:
printf(" diverted-loopback");
break;
case 2:
printf(" diverted-output");
break;
default:
printf(" diverted-?<%u>", cmd->arg1);
break;
}
break;
case O_LAYER2:
printf(" layer2");
break;
@ -1709,6 +1854,8 @@ sysctl_handler(int ac, char *av[], int which)
} else if (strncmp(*av, "dyn_keepalive", strlen(*av)) == 0) {
sysctlbyname("net.inet.ip.fw.dyn_keepalive", NULL, 0,
&which, sizeof(which));
} else if (strncmp(*av, "altq", strlen(*av)) == 0) {
altq_set_enabled(which);
} else {
warnx("unrecognize enable/disable keyword: %s\n", *av);
}
@ -1903,9 +2050,10 @@ help(void)
"set [disable N... enable N...] | move [rule] X to Y | swap X Y | show\n"
"table N {add ip[/bits] [value] | delete ip[/bits] | flush | list}\n"
"\n"
"RULE-BODY: check-state [LOG] | ACTION [LOG] ADDR [OPTION_LIST]\n"
"RULE-BODY: check-state [PARAMS] | ACTION [PARAMS] ADDR [OPTION_LIST]\n"
"ACTION: check-state | allow | count | deny | reject | skipto N |\n"
" {divert|tee} PORT | forward ADDR | pipe N | queue N\n"
"PARAMS: [log [logamount LOGLIMIT]] [altq QUEUE_NAME]\n"
"ADDR: [ MAC dst src ether_type ] \n"
" [ from IPADDR [ PORT ] to IPADDR [ PORTLIST ] ]\n"
"IPADDR: [not] { any | me | ip/bits{x,y,z} | table(t[,v]) | IPLIST }\n"
@ -2756,11 +2904,11 @@ add_ports(ipfw_insn *cmd, char *av, u_char proto, int opcode)
* Rules are added into the 'rulebuf' and then copied in the correct order
* into the actual rule.
*
* The syntax for a rule starts with the action, followed by an
* optional log action, and the various match patterns.
* The syntax for a rule starts with the action, followed by
* optional action parameters, and the various match patterns.
* In the assembled microcode, the first opcode must be an O_PROBE_STATE
* (generated if the rule includes a keep-state option), then the
* various match patterns, the "log" action, and the actual action.
* various match patterns, log/altq actions, and the actual action.
*
*/
static void
@ -2783,6 +2931,7 @@ add(int ac, char *av[])
* various flags used to record that we entered some fields.
*/
ipfw_insn *have_state = NULL; /* check-state or keep-state */
ipfw_insn *have_log = NULL, *have_altq = NULL;
size_t len;
int i;
@ -2945,32 +3094,63 @@ add(int ac, char *av[])
action = next_cmd(action);
/*
* [altq queuename] -- altq tag, optional
* [log [logamount N]] -- log, optional
*
* If exists, it goes first in the cmdbuf, but then it is
* If they exist, it go first in the cmdbuf, but then it is
* skipped in the copy section to the end of the buffer.
*/
if (ac && !strncmp(*av, "log", strlen(*av))) {
ipfw_insn_log *c = (ipfw_insn_log *)cmd;
int l;
while (ac != 0 && (i = match_token(rule_action_params, *av)) != -1) {
ac--; av++;
switch (i) {
case TOK_LOG:
{
ipfw_insn_log *c = (ipfw_insn_log *)cmd;
int l;
cmd->len = F_INSN_SIZE(ipfw_insn_log);
cmd->opcode = O_LOG;
av++; ac--;
if (ac && !strncmp(*av, "logamount", strlen(*av))) {
if (have_log)
errx(EX_DATAERR,
"log cannot be specified more than once");
have_log = (ipfw_insn *)c;
cmd->len = F_INSN_SIZE(ipfw_insn_log);
cmd->opcode = O_LOG;
if (ac && !strncmp(*av, "logamount", strlen(*av))) {
ac--; av++;
NEED1("logamount requires argument");
l = atoi(*av);
if (l < 0)
errx(EX_DATAERR,
"logamount must be positive");
c->max_log = l;
ac--; av++;
} else {
len = sizeof(c->max_log);
if (sysctlbyname("net.inet.ip.fw.verbose_limit",
&c->max_log, &len, NULL, 0) == -1)
errx(1, "sysctlbyname(\"%s\")",
"net.inet.ip.fw.verbose_limit");
}
}
break;
case TOK_ALTQ:
{
ipfw_insn_altq *a = (ipfw_insn_altq *)cmd;
NEED1("missing altq queue name");
if (have_altq)
errx(EX_DATAERR,
"altq cannot be specified more than once");
have_altq = (ipfw_insn *)a;
cmd->len = F_INSN_SIZE(ipfw_insn_altq);
cmd->opcode = O_ALTQ;
fill_altq_qid(&a->qid, *av);
ac--; av++;
NEED1("logamount requires argument");
l = atoi(*av);
if (l < 0)
errx(EX_DATAERR, "logamount must be positive");
c->max_log = l;
ac--; av++;
} else {
len = sizeof(c->max_log);
if (sysctlbyname("net.inet.ip.fw.verbose_limit",
&c->max_log, &len, NULL, 0) == -1)
errx(1, "sysctlbyname(\"%s\")",
"net.inet.ip.fw.verbose_limit");
}
break;
default:
abort();
}
cmd = next_cmd(cmd);
}
@ -3533,7 +3713,7 @@ add(int ac, char *av[])
dst = next_cmd(dst);
}
/*
* copy all commands but O_LOG, O_KEEP_STATE, O_LIMIT
* copy all commands but O_LOG, O_KEEP_STATE, O_LIMIT, O_ALTQ
*/
for (src = (ipfw_insn *)cmdbuf; src != cmd; src += i) {
i = F_LEN(src);
@ -3542,6 +3722,7 @@ add(int ac, char *av[])
case O_LOG:
case O_KEEP_STATE:
case O_LIMIT:
case O_ALTQ:
break;
default:
bcopy(src, dst, i * sizeof(uint32_t));
@ -3563,12 +3744,16 @@ add(int ac, char *av[])
rule->act_ofs = dst - rule->cmd;
/*
* put back O_LOG if necessary
* put back O_LOG, O_ALTQ if necessary
*/
src = (ipfw_insn *)cmdbuf;
if (src->opcode == O_LOG) {
i = F_LEN(src);
bcopy(src, dst, i * sizeof(uint32_t));
if (have_log) {
i = F_LEN(have_log);
bcopy(have_log, dst, i * sizeof(uint32_t));
dst += i;
}
if (have_altq) {
i = F_LEN(have_altq);
bcopy(have_altq, dst, i * sizeof(uint32_t));
dst += i;
}
/*

View File

@ -134,6 +134,7 @@ enum ipfw_opcodes { /* arguments (4 byte each) */
O_IP_DST_LOOKUP, /* arg1=table number, u32=value */
O_ANTISPOOF, /* none */
O_JAIL, /* u32 = id */
O_ALTQ, /* u32 = altq classif. qid */
O_LAST_OPCODE /* not an opcode! */
};
@ -250,6 +251,14 @@ typedef struct _ipfw_insn_pipe {
void *pipe_ptr; /* XXX */
} ipfw_insn_pipe;
/*
* This is used for storing an altq queue id number.
*/
typedef struct _ipfw_insn_altq {
ipfw_insn o;
u_int32_t qid;
} ipfw_insn_altq;
/*
* This is used for limit rules.
*/
@ -293,6 +302,7 @@ typedef struct _ipfw_insn_log {
* first instruction (at r->cmd) MUST BE an O_PROBE_STATE
* + if a rule has a "log" option, then the first action
* (at ACTION_PTR(r)) MUST be O_LOG
* + if a rule has an "altq" option, it comes after "log"
*
* NOTE: we use a simple linked list of rules because we never need
* to delete a rule without scanning the list. We do not use

View File

@ -77,6 +77,7 @@
#include <netinet/tcpip.h>
#include <netinet/udp.h>
#include <netinet/udp_var.h>
#include <altq/if_altq.h>
#ifdef IPSEC
#include <netinet6/ipsec.h>
@ -553,6 +554,13 @@ ipfw_log(struct ip_fw *f, u_int hlen, struct ether_header *eh,
if (l->log_left == 0)
limit_reached = l->max_log;
cmd += F_LEN(cmd); /* point to first action */
if (cmd->opcode == O_ALTQ) {
ipfw_insn_altq *altq = (ipfw_insn_altq *)cmd;
snprintf(SNPARGS(action2, 0), "Altq %d",
altq->qid);
cmd += F_LEN(cmd);
}
if (cmd->opcode == O_PROB)
cmd += F_LEN(cmd);
@ -1324,6 +1332,8 @@ lookup_next_rule(struct ip_fw *me)
cmd = ACTION_PTR(me);
if (cmd->opcode == O_LOG)
cmd += F_LEN(cmd);
if (cmd->opcode == O_ALTQ)
cmd += F_LEN(cmd);
if ( cmd->opcode == O_SKIPTO )
for (rule = me->next; rule ; rule = rule->next)
if (rule->rulenum >= cmd->arg1)
@ -2212,6 +2222,32 @@ ipfw_chk(struct ip_fw_args *args)
(TH_RST | TH_ACK | TH_SYN)) != TH_SYN);
break;
case O_ALTQ: {
struct altq_tag *at;
ipfw_insn_altq *altq = (ipfw_insn_altq *)cmd;
match = 1;
mtag = m_tag_get(PACKET_TAG_PF_QID,
sizeof(struct altq_tag),
M_NOWAIT);
if (mtag == NULL) {
/*
* Let the packet fall back to the
* default ALTQ.
*/
break;
}
at = (struct altq_tag *)(mtag+1);
at->qid = altq->qid;
if (hlen != 0)
at->af = AF_INET;
else
at->af = AF_LINK;
at->hdr = ip;
m_tag_prepend(m, mtag);
break;
}
case O_LOG:
if (fw_verbose)
ipfw_log(f, hlen, args->eh, m, oif);
@ -2275,6 +2311,9 @@ ipfw_chk(struct ip_fw_args *args)
* or to the SKIPTO target ('goto again' after
* having set f, cmd and l), respectively.
*
* O_LOG and O_ALTQ action parameters:
* perform some action and set match = 1;
*
* O_LIMIT and O_KEEP_STATE: these opcodes are
* not real 'actions', and are stored right
* before the 'action' part of the rule.
@ -2974,6 +3013,11 @@ check_ipfw_struct(struct ip_fw *rule, int size)
goto bad_size;
break;
case O_ALTQ:
if (cmdlen != F_INSN_SIZE(ipfw_insn_altq))
goto bad_size;
break;
case O_PIPE:
case O_QUEUE:
if (cmdlen != F_INSN_SIZE(ipfw_insn_pipe))