Implement internal (i.e. inside kernel) packet tagging using mbuf_tags(9).

Since tags are kept while packet resides in kernelspace, it's possible to
use other kernel facilities (like netgraph nodes) for altering those tags.

Submitted by:	Andrey Elsukov <bu7cher at yandex dot ru>
Submitted by:	Vadim Goncharov <vadimnuclight at tpu dot ru>
Approved by:	glebius (mentor)
Idea from:	OpenBSD PF
MFC after:	1 month
This commit is contained in:
Oleg Bulyzhin 2006-05-24 13:09:55 +00:00
parent 16a67f532d
commit 6a7d5cb645
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=158879
4 changed files with 196 additions and 10 deletions

View File

@ -1,7 +1,7 @@
.\"
.\" $FreeBSD$
.\"
.Dd May 12, 2006
.Dd May 24, 2006
.Dt IPFW 8
.Os
.Sh NAME
@ -420,6 +420,10 @@ rules is the following:
.Ar action
.Op Cm log Op Cm logamount Ar number
.Op Cm altq Ar queue
.Oo
.Bro Cm tag | untag
.Brc Ar number
.Oc
.Ar body
.Ek
.Ed
@ -552,6 +556,53 @@ 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 tag Ar number
When a packet matches a rule with the
.Cm tag
keyword, the numeric tag for the given
.Ar number
in the range 0..65535 will be attached to the packet.
The tag acts as an internal marker (it is not sent out over
the wire) that can be used to identify these packets later on.
This can be used, for example, to provide trust between interfaces
and to start doing policy-based filtering.
A packet can have mutiple tags at the same time.
Tags are "sticky", meaning once a tag is applied to a packet by a
matching rule it exists until explicit removal.
Tags are kept with the packet everywhere within the kernel, but are
lost when packet leaves the kernel, for example, on transmitting
packet out to the network or sending packet to a
.Xr divert 4
socket.
.Pp
To check for previously applied tags, use the
.Cm tagged
rule option. To delete previously applied tag, use the
.Cm untag
keyword.
.Pp
Note: since tags are kept with the packet everywhere in kernelspace,
they can be set and unset anywhere in kernel network subsystem
(using
.Xr mbuf_tags 9
facility), not only by means of
.Xr ipfw 4
.Cm tag
and
.Cm untag
keywords.
For example, there can be a specialized
.Xr netgraph 4
node doing traffic analyzing and tagging for later inspecting
in firewall.
.It Cm untag Ar number
When a packet matches a rule with the
.Cm untag
keyword, the tag with the number
.Ar number
is searched among the tags attached to this packet and,
if found, removed from it.
Other tags bound to packet, if present, are left untouched.
.It Cm altq Ar queue
When a packet matches a rule with the
.Cm altq
@ -1362,6 +1413,15 @@ specified as an argument.
.It Cm src-port Ar ports
Matches IP packets whose source port is one of the port(s)
specified as argument.
.It Cm tagged Ar tag-list
Matches packets whose tags are included in
.Ar tag-list ,
which is either a single value or a list of values or ranges
specified in the same way as
.Ar ports .
Tags can be applied to the packet using
.Cm tag
rule action parameter (see it's description for details on tags).
.It Cm tcpack Ar ack
TCP packets only.
Match if the TCP header acknowledgment number field is set to

View File

@ -82,6 +82,15 @@ int
*/
#define NEED1(msg) {if (!ac) errx(EX_USAGE, msg);}
#define NOT_NUMBER(str, msg) do { \
char *c; \
for (c = str; *c != '\0'; c++) { \
if (isdigit(*c)) \
continue; \
errx(EX_DATAERR, msg); \
} \
} while (0)
/*
* _s_x is a structure that stores a string <-> token pairs, used in
* various places in the parser. Entries are stored in arrays,
@ -212,7 +221,10 @@ enum tokens {
TOK_ALTQ,
TOK_LOG,
TOK_TAG,
TOK_UNTAG,
TOK_TAGGED,
TOK_UID,
TOK_GID,
TOK_JAIL,
@ -340,10 +352,13 @@ struct _s_x rule_actions[] = {
struct _s_x rule_action_params[] = {
{ "altq", TOK_ALTQ },
{ "log", TOK_LOG },
{ "tag", TOK_TAG },
{ "untag", TOK_UNTAG },
{ NULL, 0 } /* terminator */
};
struct _s_x rule_options[] = {
{ "tagged", TOK_TAGGED },
{ "uid", TOK_UID },
{ "gid", TOK_GID },
{ "jail", TOK_JAIL },
@ -572,6 +587,7 @@ struct _s_x _port_name[] = {
{"ipttl", O_IPTTL},
{"mac-type", O_MAC_TYPE},
{"tcpdatalen", O_TCPDATALEN},
{"tagged", O_TAGGED},
{NULL, 0}
};
@ -1358,7 +1374,7 @@ show_ipfw(struct ip_fw *rule, int pcwidth, int bcwidth)
{
static int twidth = 0;
int l;
ipfw_insn *cmd;
ipfw_insn *cmd, *tagptr = NULL;
char *comment = NULL; /* ptr to comment if we have one */
int proto = 0; /* default */
int flags = 0; /* prerequisites */
@ -1500,6 +1516,10 @@ show_ipfw(struct ip_fw *rule, int pcwidth, int bcwidth)
altqptr = (ipfw_insn_altq *)cmd;
break;
case O_TAG:
tagptr = cmd;
break;
default:
printf("** unrecognized action %d len %d ",
cmd->opcode, cmd->len);
@ -1520,6 +1540,12 @@ show_ipfw(struct ip_fw *rule, int pcwidth, int bcwidth)
else
printf(" altq %s", qname);
}
if (tagptr) {
if (tagptr->len & F_NOT)
printf(" untag %hu", tagptr->arg1);
else
printf(" tag %hu", tagptr->arg1);
}
/*
* then print the body.
@ -1906,6 +1932,14 @@ show_ipfw(struct ip_fw *rule, int pcwidth, int bcwidth)
print_ext6hdr( (ipfw_insn *) cmd );
break;
case O_TAGGED:
if (F_LEN(cmd) == 1)
printf(" tagged %hu", cmd->arg1 );
else
print_newports((ipfw_insn_u16 *)cmd, 0,
O_TAGGED);
break;
default:
printf(" [opcode %d len %d]",
cmd->opcode, cmd->len);
@ -3775,7 +3809,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;
ipfw_insn *have_log = NULL, *have_altq = NULL, *have_tag = NULL;
size_t len;
int i;
@ -4016,6 +4050,20 @@ add(int ac, char *av[])
}
break;
case TOK_TAG:
case TOK_UNTAG:
{
if (have_tag)
errx(EX_USAGE, "tag and untag cannot be specified more than once");
NEED1("missing tag number");
NOT_NUMBER(*av, "invalid tag number");
have_tag = cmd;
fill_cmd(cmd, O_TAG, (i == TOK_TAG) ? 0: F_NOT,
strtoul(*av, NULL, 0));
ac--; av++;
}
break;
default:
abort();
}
@ -4605,6 +4653,19 @@ add(int ac, char *av[])
ac = 0;
break;
case TOK_TAGGED:
NEED1("missing tag number");
if (strpbrk(*av, "-,")) {
if (!add_ports(cmd, *av, 0, O_TAGGED))
errx(EX_DATAERR, "invalid tag %s", *av);
} else {
NOT_NUMBER(*av, "invalid tag number");
fill_cmd(cmd, O_TAGGED, 0,
strtoul(*av, NULL, 0));
}
ac--; av++;
break;
default:
errx(EX_USAGE, "unrecognised option [%d] %s\n", i, s);
}
@ -4641,9 +4702,8 @@ add(int ac, char *av[])
fill_cmd(dst, O_PROBE_STATE, 0, 0);
dst = next_cmd(dst);
}
/*
* copy all commands but O_LOG, O_KEEP_STATE, O_LIMIT, O_ALTQ
*/
/* copy all commands but O_LOG, O_KEEP_STATE, O_LIMIT, O_ALTQ, O_TAG */
for (src = (ipfw_insn *)cmdbuf; src != cmd; src += i) {
i = F_LEN(src);
@ -4652,6 +4712,7 @@ add(int ac, char *av[])
case O_KEEP_STATE:
case O_LIMIT:
case O_ALTQ:
case O_TAG:
break;
default:
bcopy(src, dst, i * sizeof(uint32_t));
@ -4672,9 +4733,7 @@ add(int ac, char *av[])
*/
rule->act_ofs = dst - rule->cmd;
/*
* put back O_LOG, O_ALTQ if necessary
*/
/* put back O_LOG, O_ALTQ, O_TAG if necessary */
if (have_log) {
i = F_LEN(have_log);
bcopy(have_log, dst, i * sizeof(uint32_t));
@ -4685,6 +4744,11 @@ add(int ac, char *av[])
bcopy(have_altq, dst, i * sizeof(uint32_t));
dst += i;
}
if (have_tag) {
i = F_LEN(have_tag);
bcopy(have_tag, dst, i * sizeof(uint32_t));
dst += i;
}
/*
* copy all other actions
*/

View File

@ -157,6 +157,9 @@ enum ipfw_opcodes { /* arguments (4 byte each) */
O_UNREACH6, /* arg1=icmpv6 code arg (deny) */
O_TAG, /* arg1=tag number */
O_TAGGED, /* arg1=tag number */
O_LAST_OPCODE /* not an opcode! */
};
@ -215,6 +218,8 @@ typedef struct _ipfw_insn { /* template for instructions */
*/
#define F_INSN_SIZE(t) ((sizeof (t))/sizeof(u_int32_t))
#define MTAG_IPFW 1148380143 /* IPFW-tagged cookie */
/*
* This is used to store an array of 16-bit entries (ports etc.)
*/
@ -346,6 +351,7 @@ typedef struct _ipfw_insn_icmp6 {
* + 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"
* + if a rule has an O_TAG option, it comes after "log" and "altq"
*
* 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

@ -781,6 +781,9 @@ ipfw_log(struct ip_fw *f, u_int hlen, struct ip_fw_args *args,
if (cmd->opcode == O_PROB)
cmd += F_LEN(cmd);
if (cmd->opcode == O_TAG)
cmd += F_LEN(cmd);
action = action2;
switch (cmd->opcode) {
case O_DENY:
@ -1660,6 +1663,8 @@ lookup_next_rule(struct ip_fw *me)
cmd += F_LEN(cmd);
if (cmd->opcode == O_ALTQ)
cmd += F_LEN(cmd);
if (cmd->opcode == O_TAG)
cmd += F_LEN(cmd);
if ( cmd->opcode == O_SKIPTO )
for (rule = me->next; rule ; rule = rule->next)
if (rule->rulenum >= cmd->arg1)
@ -2886,6 +2891,55 @@ do { \
match = is_ipv4;
break;
case O_TAG:
/* Packet is already tagged with this tag? */
mtag = m_tag_locate(m, MTAG_IPFW,
((ipfw_insn *) cmd)->arg1, NULL);
/* We have `untag' action when F_NOT flag is
* present. And we must remove this mtag from
* mbuf and reset `match' to zero (`match' will
* be inversed later).
* Otherwise we should allocate new mtag and
* push it into mbuf.
*/
if (cmd->len & F_NOT) { /* `untag' action */
if (mtag != NULL)
m_tag_delete(m, mtag);
} else if (mtag == NULL) {
mtag = m_tag_alloc(MTAG_IPFW,
((ipfw_insn *) cmd)->arg1, 0, M_NOWAIT);
if (mtag != NULL)
m_tag_prepend(m, mtag);
}
match = (cmd->len & F_NOT) ? 0: 1;
break;
case O_TAGGED:
if (cmdlen == 1) {
match = (m_tag_locate(m, MTAG_IPFW,
((ipfw_insn *) cmd)->arg1, NULL) != NULL);
break;
}
/* we have ranges */
for (mtag = m_tag_first(m);
mtag != NULL && !match;
mtag = m_tag_next(m, mtag)) {
uint16_t *p;
int i;
if (mtag->m_tag_cookie != MTAG_IPFW)
continue;
p = ((ipfw_insn_u16 *)cmd)->ports;
i = cmdlen - 1;
for(; !match && i > 0; i--, p += 2)
match =
mtag->m_tag_id >= p[0] &&
mtag->m_tag_id <= p[1];
}
break;
/*
* The second set of opcodes represents 'actions',
* i.e. the terminal part of a rule once the packet
@ -2905,7 +2959,7 @@ do { \
* or to the SKIPTO target ('goto again' after
* having set f, cmd and l), respectively.
*
* O_LOG and O_ALTQ action parameters:
* O_TAG, O_LOG and O_ALTQ action parameters:
* perform some action and set match = 1;
*
* O_LIMIT and O_KEEP_STATE: these opcodes are
@ -3528,6 +3582,7 @@ check_ipfw_struct(struct ip_fw *rule, int size)
case O_IP6:
#endif
case O_IP4:
case O_TAG:
if (cmdlen != F_INSN_SIZE(ipfw_insn))
goto bad_size;
break;
@ -3600,6 +3655,7 @@ check_ipfw_struct(struct ip_fw *rule, int size)
case O_IPTTL:
case O_IPLEN:
case O_TCPDATALEN:
case O_TAGGED:
if (cmdlen < 1 || cmdlen > 31)
goto bad_size;
break;