Add ipfw support for setting/matching DiffServ codepoints (DSCP).

Setting DSCP support is done via O_SETDSCP which works for both
IPv4 and IPv6 packets. Fast checksum recalculation (RFC 1624) is done for IPv4.
Dscp can be specified by name (AFXY, CSX, BE, EF), by value
(0..63) or via tablearg.

Matching DSCP is done via another opcode (O_DSCP) which accepts several
classes at once (af11,af22,be). Classes are stored in bitmask (2 u32 words).

Many people made their variants of this patch, the ones I'm aware of are
(in alphabetic order):

Dmitrii Tejblum
Marcelo Araujo
Roman Bogorodskiy (novel)
Sergey Matveichuk (sem)
Sergey Ryabin

PR:		kern/102471, kern/121122
MFC after:	2 weeks
This commit is contained in:
Alexander V. Chernikov 2013-03-20 10:35:33 +00:00
parent 6991ee13a6
commit ae01d73c04
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=248552
7 changed files with 283 additions and 4 deletions

View File

@ -960,6 +960,61 @@ It is possible to use the
keyword with setfib.
If the tablearg value is not within the compiled range of fibs,
the packet's fib is set to 0.
.It Cm setdscp Ar DSCP | number | tablearg
Set specified DiffServ codepoint for an IPv4/IPv6 packet.
Processing continues at the next rule.
Supported values are:
.Pp
.Cm CS0
.Pq Dv 000000 ,
.Cm CS1
.Pq Dv 001000 ,
.Cm CS2
.Pq Dv 010000 ,
.Cm CS3
.Pq Dv 011000 ,
.Cm CS4
.Pq Dv 100000 ,
.Cm CS5
.Pq Dv 101000 ,
.Cm CS6
.Pq Dv 110000 ,
.Cm CS7
.Pq Dv 111000 ,
.Cm AF11
.Pq Dv 001010 ,
.Cm AF12
.Pq Dv 001100 ,
.Cm AF13
.Pq Dv 001110 ,
.Cm AF21
.Pq Dv 010010 ,
.Cm AF22
.Pq Dv 010100 ,
.Cm AF23
.Pq Dv 010110 ,
.Cm AF31
.Pq Dv 011010 ,
.Cm AF32
.Pq Dv 011100 ,
.Cm AF33
.Pq Dv 011110 ,
.Cm AF41
.Pq Dv 100010 ,
.Cm AF42
.Pq Dv 100100 ,
.Cm AF43
.Pq Dv 100110 ,
.Cm EF
.Pq Dv 101110 ,
.Cm BE
.Pq Dv 000000 .
Additionally, DSCP value can be specified by number (0..64).
It is also possible to use the
.Cm tablearg
keyword with setdscp.
If the tablearg value is not within the 0..64 range, lower 6 bits of supplied
value are used.
.It Cm reass
Queue and reassemble IP fragments.
If the packet is not fragmented, counters are updated and
@ -1454,6 +1509,17 @@ The supported IP types of service are:
The absence of a particular type may be denoted
with a
.Ql \&! .
.It Cm dscp spec Ns Op , Ns Ar spec
Matches IPv4/IPv6 packets whose
.Cm DS
field value is contained in
.Ar spec
mask.
Multiple values can be specified via
the comma separated list.
Value can be one of keywords used in
.Cm setdscp
action or exact number.
.It Cm ipttl Ar ttl-list
Matches IPv4 packets whose time to live is included in
.Ar ttl-list ,
@ -2976,6 +3042,23 @@ configured on
but coming in on
.Li fxp1
would be dropped.
.Pp
The
.Cm setdscp
option could be used to (re)mark user traffic,
by adding the following to the appropriate place in ruleset:
.Pp
.Dl "ipfw add setdscp be ip from any to any dscp af11,af21"
.Pp
This rule drops all incoming packets that appear to be coming from another
directly connected system but on the wrong interface.
For example, a packet with a source address of
.Li 192.168.0.0/24 ,
configured on
.Li fxp0 ,
but coming in on
.Li fxp1
would be dropped.
.Ss DYNAMIC RULES
In order to protect a site from flood attacks involving fake
TCP packets, it is safer to use dynamic rules:

View File

@ -167,6 +167,32 @@ static struct _s_x f_iptos[] = {
{ NULL, 0 }
};
static struct _s_x f_ipdscp[] = {
{ "af11", IPTOS_DSCP_AF11 >> 2 }, /* 001010 */
{ "af12", IPTOS_DSCP_AF12 >> 2 }, /* 001100 */
{ "af13", IPTOS_DSCP_AF13 >> 2 }, /* 001110 */
{ "af21", IPTOS_DSCP_AF21 >> 2 }, /* 010010 */
{ "af22", IPTOS_DSCP_AF22 >> 2 }, /* 010100 */
{ "af23", IPTOS_DSCP_AF23 >> 2 }, /* 010110 */
{ "af31", IPTOS_DSCP_AF31 >> 2 }, /* 011010 */
{ "af32", IPTOS_DSCP_AF32 >> 2 }, /* 011100 */
{ "af33", IPTOS_DSCP_AF33 >> 2 }, /* 011110 */
{ "af41", IPTOS_DSCP_AF41 >> 2 }, /* 100010 */
{ "af42", IPTOS_DSCP_AF42 >> 2 }, /* 100100 */
{ "af43", IPTOS_DSCP_AF43 >> 2 }, /* 100110 */
{ "be", IPTOS_DSCP_CS0 >> 2 }, /* 000000 */
{ "ef", IPTOS_DSCP_EF >> 2 }, /* 101110 */
{ "cs0", IPTOS_DSCP_CS0 >> 2 }, /* 000000 */
{ "cs1", IPTOS_DSCP_CS1 >> 2 }, /* 001000 */
{ "cs2", IPTOS_DSCP_CS2 >> 2 }, /* 010000 */
{ "cs3", IPTOS_DSCP_CS3 >> 2 }, /* 011000 */
{ "cs4", IPTOS_DSCP_CS4 >> 2 }, /* 100000 */
{ "cs5", IPTOS_DSCP_CS5 >> 2 }, /* 101000 */
{ "cs6", IPTOS_DSCP_CS6 >> 2 }, /* 110000 */
{ "cs7", IPTOS_DSCP_CS7 >> 2 }, /* 100000 */
{ NULL, 0 }
};
static struct _s_x limit_masks[] = {
{"all", DYN_SRC_ADDR|DYN_SRC_PORT|DYN_DST_ADDR|DYN_DST_PORT},
{"src-addr", DYN_SRC_ADDR},
@ -237,6 +263,7 @@ static struct _s_x rule_actions[] = {
{ "nat", TOK_NAT },
{ "reass", TOK_REASS },
{ "setfib", TOK_SETFIB },
{ "setdscp", TOK_SETDSCP },
{ "call", TOK_CALL },
{ "return", TOK_RETURN },
{ NULL, 0 } /* terminator */
@ -714,6 +741,51 @@ fill_newports(ipfw_insn_u16 *cmd, char *av, int proto, int cblen)
return (i);
}
/*
* Fill the body of the command with the list of DiffServ codepoints.
*/
static void
fill_dscp(ipfw_insn *cmd, char *av, int cblen)
{
uint32_t *low, *high;
char *s = av, *a;
int code;
cmd->opcode = O_DSCP;
cmd->len |= F_INSN_SIZE(ipfw_insn_u32) + 1;
CHECK_CMDLEN;
low = (uint32_t *)(cmd + 1);
high = low + 1;
*low = 0;
*high = 0;
while (s != NULL) {
a = strchr(s, ',');
if (a != NULL)
*a++ = '\0';
if (isalpha(*s)) {
if ((code = match_token(f_ipdscp, s)) == -1)
errx(EX_DATAERR, "Unknown DSCP code");
} else {
code = strtoul(s, NULL, 10);
if (code < 0 || code > 63)
errx(EX_DATAERR, "Invalid DSCP value");
}
if (code > 32)
*high |= 1 << (code - 32);
else
*low |= 1 << code;
s = a;
}
}
static struct _s_x icmpcodes[] = {
{ "net", ICMP_UNREACH_NET },
{ "host", ICMP_UNREACH_HOST },
@ -972,6 +1044,32 @@ print_icmptypes(ipfw_insn_u32 *cmd)
}
}
static void
print_dscp(ipfw_insn_u32 *cmd)
{
int i, c;
uint32_t *v;
char sep= ' ';
const char *code;
printf(" dscp");
i = 0;
c = 0;
v = cmd->d;
while (i < 64) {
if (*v & (1 << i)) {
if ((code = match_value(f_ipdscp, i)) != NULL)
printf("%c%s", sep, code);
else
printf("%c%d", sep, i);
sep = ',';
}
if ((++i % 32) == 0)
v++;
}
}
/*
* show_ipfw() prints the body of an ipfw rule.
* Because the standard rule has at least proto src_ip dst_ip, we use
@ -1205,6 +1303,17 @@ show_ipfw(struct ip_fw *rule, int pcwidth, int bcwidth)
PRINT_UINT_ARG("setfib ", cmd->arg1);
break;
case O_SETDSCP:
{
const char *code;
if ((code = match_value(f_ipdscp, cmd->arg1)) != NULL)
printf("setdscp %s", code);
else
PRINT_UINT_ARG("setdscp ", cmd->arg1);
}
break;
case O_REASS:
printf("reass");
break;
@ -1500,6 +1609,10 @@ show_ipfw(struct ip_fw *rule, int pcwidth, int bcwidth)
printf(" ipprecedence %u", (cmd->arg1) >> 5 );
break;
case O_DSCP:
print_dscp((ipfw_insn_u32 *)cmd);
break;
case O_IPLEN:
if (F_LEN(cmd) == 1)
printf(" iplen %u", cmd->arg1 );
@ -3036,6 +3149,24 @@ chkarg:
break;
}
case TOK_SETDSCP:
{
int code;
action->opcode = O_SETDSCP;
NEED1("missing DSCP code");
if (_substrcmp(*av, "tablearg") == 0) {
action->arg1 = IP_FW_TABLEARG;
} else if (isalpha(*av[0])) {
if ((code = match_token(f_ipdscp, *av)) == -1)
errx(EX_DATAERR, "Unknown DSCP code");
action->arg1 = code;
} else
action->arg1 = strtoul(*av, NULL, 10);
av++;
break;
}
case TOK_REASS:
action->opcode = O_REASS;
break;
@ -3448,6 +3579,12 @@ read_options:
av++;
break;
case TOK_DSCP:
NEED1("missing DSCP code");
fill_dscp(cmd, *av, cblen);
av++;
break;
case TOK_IPOPTS:
NEED1("missing argument for ipoptions");
fill_flags(cmd, O_IPOPT, f_ipopts, *av);

View File

@ -203,6 +203,7 @@ enum tokens {
TOK_SETFIB,
TOK_LOOKUP,
TOK_SOCKARG,
TOK_SETDSCP,
};
/*
* the following macro returns an error message if we run out of

View File

@ -218,6 +218,9 @@ enum ipfw_opcodes { /* arguments (4 byte each) */
O_FORWARD_IP6, /* fwd sockaddr_in6 */
O_DSCP, /* 2 u32 = DSCP mask */
O_SETDSCP, /* arg1=DSCP value */
O_LAST_OPCODE /* not an opcode! */
};

View File

@ -1624,6 +1624,32 @@ do { \
flags_match(cmd, ip->ip_tos));
break;
case O_DSCP:
{
uint32_t *p;
uint16_t x;
p = ((ipfw_insn_u32 *)cmd)->d;
if (is_ipv4)
x = ip->ip_tos >> 2;
else if (is_ipv6) {
uint8_t *v;
v = &((struct ip6_hdr *)ip)->ip6_vfc;
x = (*v & 0x0F) << 2;
v++;
x |= *v >> 6;
} else
break;
/* DSCP bitmask is stored as low_u32 high_u32 */
if (x > 32)
match = *(p + 1) & (1 << (x - 32));
else
match = *p & (1 << x);
}
break;
case O_TCPDATALEN:
if (proto == IPPROTO_TCP && offset == 0) {
struct tcphdr *tcp;
@ -2353,6 +2379,32 @@ do { \
break;
}
case O_SETDSCP: {
uint16_t code;
code = IP_FW_ARG_TABLEARG(cmd->arg1) & 0x3F;
l = 0; /* exit inner loop */
if (is_ipv4) {
uint16_t a;
a = ip->ip_tos;
ip->ip_tos = (code << 2) | (ip->ip_tos & 0x03);
a += ntohs(ip->ip_sum) - ip->ip_tos;
ip->ip_sum = htons(a);
} else if (is_ipv6) {
uint8_t *v;
v = &((struct ip6_hdr *)ip)->ip6_vfc;
*v = (*v & 0xF0) | (code >> 2);
v++;
*v = (*v & 0x3F) | ((code & 0x03) << 6);
} else
break;
IPFW_INC_RULE_COUNTER(f, pktlen);
break;
}
case O_NAT:
if (!IPFW_NAT_LOADED) {
retval = IP_FW_DENY;

View File

@ -292,10 +292,8 @@ ipfw_log(struct ip_fw *f, u_int hlen, struct ip_fw_args *args,
altq->qid);
cmd += F_LEN(cmd);
}
if (cmd->opcode == O_PROB)
cmd += F_LEN(cmd);
if (cmd->opcode == O_TAG)
if (cmd->opcode == O_PROB || cmd->opcode == O_TAG ||
cmd->opcode == O_SETDSCP)
cmd += F_LEN(cmd);
action = action2;

View File

@ -671,6 +671,10 @@ check_ipfw_struct(struct ip_fw *rule, int size)
case O_IPID:
case O_IPTTL:
case O_IPLEN:
case O_DSCP:
if (cmdlen != F_INSN_SIZE(ipfw_insn_u32) + 1)
goto bad_size;
break;
case O_TCPDATALEN:
case O_TCPWIN:
case O_TAGGED:
@ -738,6 +742,7 @@ check_ipfw_struct(struct ip_fw *rule, int size)
case O_ACCEPT:
case O_DENY:
case O_REJECT:
case O_SETDSCP:
#ifdef INET6
case O_UNREACH6:
#endif