ipfw: add [fw]mark implementation for ipfw

Packet Mark is an analogue to ipfw tags with O(1) lookup from mbuf while
regular tags require a single-linked list traversal.
Mark is a 32-bit number that can be looked up in a table
[with 'number' table-type], matched or compared with a number with optional
mask applied before comparison.
Having generic nature, Mark can be used in a variety of needs.
For example, it could be used as a security group: mark will hold a security
group id and represent a group of packet flows that shares same access
control policy.

Reviewed By: pauamma_gundo.com
Differential Revision: https://reviews.freebsd.org/D39555
MFC after:	1 month
This commit is contained in:
Boris Lytochkin
2023-04-25 12:38:36 +00:00
committed by Alexander V. Chernikov
parent 089104e0e0
commit fc727ad63d
13 changed files with 259 additions and 54 deletions
+48 -6
View File
@@ -759,7 +759,7 @@ A packet can have multiple 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
lost when the packet leaves the kernel, for example, on transmitting
packet out to the network or sending packet to a
.Xr divert 4
socket.
@@ -793,6 +793,27 @@ keyword, the tag with the 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 setmark Ar value | tablearg
When a packet matches a rule with the
.Cm setmark
keyword, a 32-bit numeric mark is assigned to the packet.
The mark is an extension to the tags.
As tags, mark is "sticky" so the value is kept the same within the kernel and
is lost when the packet leaves the kernel.
Unlike tags, mark can be matched as a lookup table key or compared with bitwise
mask applied against another value.
Each packet can have only one mark, so
.Cm setmark
always overwrites the previous mark value.
.Pp
The initial mark value is 0.
To check the current mark value, use the
.Cm mark
rule option.
Mark
.Ar value
can be entered as decimal or hexadecimal (if prefixed by 0x), and they
are always printed as hexadecimal.
.It Cm altq Ar queue
When a packet matches a rule with the
.Cm altq
@@ -1845,7 +1866,8 @@ set of parameters as specified in the rule.
One or more
of source and destination addresses and ports can be
specified.
.It Cm lookup Bro Cm dst-ip | dst-port | dst-mac | src-ip | src-port | src-mac | uid | jail Brc Ar name
.It Cm lookup Bro Cm dst-ip | dst-port | dst-mac | src-ip | src-port | src-mac | uid |
.Cm jail | dscp | mark Brc Ar name
Search an entry in lookup table
.Ar name
that matches the field specified as argument.
@@ -2017,6 +2039,23 @@ specified in the same way as
Tags can be applied to the packet using
.Cm tag
rule action parameter (see it's description for details on tags).
.It Cm mark Ar value[:bitmask] | tablearg[:bitmask]
Matches packets whose mark is equal to
.Ar value
with optional
.Ar bitmask
applied to it.
.Cm tablearg
can also be used instead of an explicit
.Ar value
to match a value supplied by the last table lookup.
.Pp
Both
.Ar value
and
.Ar bitmask
can be entered as decimal or hexadecimal (if prefixed by 0x), and they
are always printed as hexadecimal.
.It Cm tcpack Ar ack
TCP packets only.
Match if the TCP header acknowledgment number field is set to
@@ -2359,7 +2398,7 @@ The following value types are supported:
.Bl -tag -width indent
.It Ar value-mask : Ar value-type Ns Op , Ns Ar value-mask
.It Ar value-type : Ar skipto | pipe | fib | nat | dscp | tag | divert |
.Ar netgraph | limit | ipv4
.Ar netgraph | limit | ipv4 | ipv6 | mark
.It Cm skipto
rule number to jump to.
.It Cm pipe
@@ -2382,16 +2421,19 @@ maximum number of connections.
IPv4 nexthop to fwd packets to.
.It Cm ipv6
IPv6 nexthop to fwd packets to.
.It Cm mark
mark value to match/set.
.El
.Pp
The
.Cm tablearg
argument can be used with the following actions:
.Cm nat, pipe, queue, divert, tee, netgraph, ngtee, fwd, skipto, setfib ,
.Cm setmark ,
action parameters:
.Cm tag, untag ,
rule options:
.Cm limit, tagged .
.Cm limit, tagged, mark .
.Pp
When used with the
.Cm skipto
@@ -3326,8 +3368,8 @@ Obey transparent proxy rules only, packet aliasing is not performed.
.It Cm skip_global
Skip instance in case of global state lookup (see below).
.It Cm port_range Ar lower-upper
Set the aliasing ports between the ranges given. Upper port has to be greater
than lower.
Set the aliasing ports between the ranges given.
Upper port has to be greater than lower.
.El
.Pp
Some special values can be supplied instead of
+86 -1
View File
@@ -288,6 +288,7 @@ static struct _s_x rule_actions[] = {
{ "return", TOK_RETURN },
{ "eaction", TOK_EACTION },
{ "tcp-setmss", TOK_TCPSETMSS },
{ "setmark", TOK_SETMARK },
{ NULL, 0 } /* terminator */
};
@@ -313,6 +314,7 @@ static struct _s_x lookup_keys[] = {
{ "uid", LOOKUP_UID },
{ "jail", LOOKUP_JAIL },
{ "dscp", LOOKUP_DSCP },
{ "mark", LOOKUP_MARK },
{ NULL, 0 },
};
@@ -391,6 +393,7 @@ static struct _s_x rule_options[] = {
{ "src-ip6", TOK_SRCIP6 },
{ "lookup", TOK_LOOKUP },
{ "flow", TOK_FLOW },
{ "mark", TOK_MARK },
{ "defer-action", TOK_SKIPACTION },
{ "defer-immediate-action", TOK_SKIPACTION },
{ "//", TOK_COMMENT },
@@ -1102,6 +1105,45 @@ fill_dscp(ipfw_insn *cmd, char *av, int cblen)
}
}
/*
* Fill the body of the command with mark value and mask.
*/
static void
fill_mark(ipfw_insn *cmd, char *av, int cblen)
{
uint32_t *value, *mask;
char *value_str;
cmd->opcode = O_MARK;
cmd->len |= F_INSN_SIZE(ipfw_insn_u32) + 1;
CHECK_CMDLEN;
value = (uint32_t *)(cmd + 1);
mask = value + 1;
value_str = strsep(&av, ":");
if (strcmp(value_str, "tablearg") == 0) {
cmd->arg1 = IP_FW_TARG;
*value = 0;
} else {
/* This is not a tablearg */
cmd->arg1 |= 0x8000;
*value = strtoul(value_str, NULL, 0);
}
if (av)
*mask = strtoul(av, NULL, 0);
else
*mask = 0xFFFFFFFF;
if ((*value & *mask) != *value)
errx(EX_DATAERR, "Static mark value: some bits in value are"
" set that will be masked out by mask "
"(%#x & %#x) = %#x != %#x",
*value, *mask, (*value & *mask), *value);
}
static struct _s_x icmpcodes[] = {
{ "net", ICMP_UNREACH_NET },
{ "host", ICMP_UNREACH_HOST },
@@ -1788,6 +1830,19 @@ print_instruction(struct buf_pr *bp, const struct format_opts *fo,
case O_SKIP_ACTION:
bprintf(bp, " defer-immediate-action");
break;
case O_MARK:
bprintf(bp, " mark");
if (cmd->arg1 == IP_FW_TARG)
bprintf(bp, " tablearg");
else
bprintf(bp, " %#x",
((const ipfw_insn_u32 *)cmd)->d[0]);
if (((const ipfw_insn_u32 *)cmd)->d[1] != 0xFFFFFFFF)
bprintf(bp, ":%#x",
((const ipfw_insn_u32 *)cmd)->d[1]);
break;
default:
bprintf(bp, " [opcode %d len %d]", cmd->opcode,
cmd->len);
@@ -2031,6 +2086,13 @@ print_action_instruction(struct buf_pr *bp, const struct format_opts *fo,
else
bprint_uint_arg(bp, "call ", cmd->arg1);
break;
case O_SETMARK:
if (cmd->arg1 == IP_FW_TARG) {
bprintf(bp, "setmark tablearg");
break;
}
bprintf(bp, "setmark %#x", ((const ipfw_insn_u32 *)cmd)->d[0]);
break;
default:
bprintf(bp, "** unrecognized action %d len %d ",
cmd->opcode, cmd->len);
@@ -2175,7 +2237,7 @@ static const int action_opcodes[] = {
O_CHECK_STATE, O_ACCEPT, O_COUNT, O_DENY, O_REJECT,
O_UNREACH6, O_SKIPTO, O_PIPE, O_QUEUE, O_DIVERT, O_TEE,
O_NETGRAPH, O_NGTEE, O_FORWARD_IP, O_FORWARD_IP6, O_NAT,
O_SETFIB, O_SETDSCP, O_REASS, O_CALLRETURN,
O_SETFIB, O_SETDSCP, O_REASS, O_CALLRETURN, O_SETMARK,
/* keep the following opcodes at the end of the list */
O_EXTERNAL_ACTION, O_EXTERNAL_INSTANCE, O_EXTERNAL_DATA
};
@@ -4244,6 +4306,23 @@ compile_rule(char *av[], uint32_t *rbuf, int *rbufsize, struct tidx *tstate)
fill_cmd(action, O_CALLRETURN, F_NOT, 0);
break;
case TOK_SETMARK: {
action->opcode = O_SETMARK;
action->len = F_INSN_SIZE(ipfw_insn_u32);
NEED1("missing mark");
if (strcmp(*av, "tablearg") == 0) {
action->arg1 = IP_FW_TARG;
} else {
((ipfw_insn_u32 *)action)->d[0] =
strtoul(*av, NULL, 0);
/* This is not a tablearg */
action->arg1 |= 0x8000;
}
av++;
CHECK_CMDLEN;
break;
}
case TOK_TCPSETMSS: {
u_long mss;
uint16_t idx;
@@ -5131,6 +5210,12 @@ compile_rule(char *av[], uint32_t *rbuf, int *rbufsize, struct tidx *tstate)
fill_cmd(cmd, O_SKIP_ACTION, 0, 0);
break;
case TOK_MARK:
NEED1("missing mark value:mask");
fill_mark(cmd, *av, cblen);
av++;
break;
default:
errx(EX_USAGE, "unrecognised option [%d] %s\n", i, s);
}
+3
View File
@@ -321,6 +321,9 @@ enum tokens {
TOK_TCPSETMSS,
TOK_MARK,
TOK_SETMARK,
TOK_SKIPACTION,
};
+19 -30
View File
@@ -106,6 +106,7 @@ static struct _s_x tablevaltypes[] = {
{ "limit", IPFW_VTYPE_LIMIT },
{ "ipv4", IPFW_VTYPE_NH4 },
{ "ipv6", IPFW_VTYPE_NH6 },
{ "mark", IPFW_VTYPE_MARK },
{ NULL, 0 }
};
@@ -916,7 +917,7 @@ table_do_modify_record(int cmd, ipfw_obj_header *oh,
memcpy(pbuf, oh, sizeof(*oh));
oh = (ipfw_obj_header *)pbuf;
oh->opheader.version = 1;
oh->opheader.version = 1; /* Current version */
ctlv = (ipfw_obj_ctlv *)(oh + 1);
ctlv->count = count;
@@ -1662,6 +1663,11 @@ tentry_fill_value(ipfw_obj_header *oh __unused, ipfw_obj_tentry *tent,
}
etype = "ipv6";
break;
case IPFW_VTYPE_MARK:
v->mark = strtol(n, &e, 16);
if (*e != '\0')
etype = "mark";
break;
}
if (etype != NULL)
@@ -1878,6 +1884,9 @@ table_show_value(char *buf, size_t bufsize, ipfw_table_value *v,
NI_NUMERICHOST) == 0)
l = snprintf(buf, sz, "%s,", abuf);
break;
case IPFW_VTYPE_MARK:
l = snprintf(buf, sz, "%#x,", v->mark);
break;
}
buf += l;
@@ -2034,37 +2043,17 @@ ipfw_list_ta(int ac __unused, char *av[] __unused)
}
/* Copy of current kernel table_value structure */
struct _table_value {
uint32_t tag; /* O_TAG/O_TAGGED */
uint32_t pipe; /* O_PIPE/O_QUEUE */
uint16_t divert; /* O_DIVERT/O_TEE */
uint16_t skipto; /* skipto, CALLRET */
uint32_t netgraph; /* O_NETGRAPH/O_NGTEE */
uint32_t fib; /* O_SETFIB */
uint32_t nat; /* O_NAT */
uint32_t nh4;
uint8_t dscp;
uint8_t spare0;
uint16_t spare1;
/* -- 32 bytes -- */
struct in6_addr nh6;
uint32_t limit; /* O_LIMIT */
uint32_t zoneid;
uint64_t refcnt; /* Number of references */
};
static int
compare_values(const void *_a, const void *_b)
{
const struct _table_value *a, *b;
const ipfw_table_value *a, *b;
a = (const struct _table_value *)_a;
b = (const struct _table_value *)_b;
a = (const ipfw_table_value *)_a;
b = (const ipfw_table_value *)_b;
if (a->spare1 < b->spare1)
if (a->kidx < b->kidx)
return (-1);
else if (a->spare1 > b->spare1)
else if (a->kidx > b->kidx)
return (1);
return (0);
@@ -2075,7 +2064,7 @@ ipfw_list_values(int ac __unused, char *av[] __unused)
{
char buf[128];
ipfw_obj_lheader *olh;
struct _table_value *v;
ipfw_table_value *v;
uint32_t i, vmask;
int error;
@@ -2087,13 +2076,13 @@ ipfw_list_values(int ac __unused, char *av[] __unused)
table_print_valheader(buf, sizeof(buf), vmask);
printf("HEADER: %s\n", buf);
v = (struct _table_value *)(olh + 1);
v = (ipfw_table_value *)(olh + 1);
qsort(v, olh->count, olh->objsize, compare_values);
for (i = 0; i < olh->count; i++) {
table_show_value(buf, sizeof(buf), (ipfw_table_value *)v,
vmask, 0);
printf("[%u] refs=%lu %s\n", v->spare1, (u_long)v->refcnt, buf);
v = (struct _table_value *)((caddr_t)v + olh->objsize);
printf("[%u] refs=%lu %s\n", v->kidx, (u_long)v->refcnt, buf);
v = (ipfw_table_value *)((caddr_t)v + olh->objsize);
}
free(olh);