diff --git a/sbin/ipfw/ipfw.8 b/sbin/ipfw/ipfw.8 index fcba4a4b03f2..bcc8017a571b 100644 --- a/sbin/ipfw/ipfw.8 +++ b/sbin/ipfw/ipfw.8 @@ -1,7 +1,7 @@ .\" .\" $FreeBSD$ .\" -.Dd May 24, 2019 +.Dd June 21, 2019 .Dt IPFW 8 .Os .Sh NAME @@ -1989,6 +1989,12 @@ a non-zero offset. See the .Cm frag option for details on matching fragmented packets. +.It Cm tcpmss Ar tcpmss-list +Matches TCP packets whose MSS (maximum segment size) value is set to +.Ar tcpmss-list , +which is either a single value or a list of values or ranges +specified in the same way as +.Ar ports . .It Cm tcpseq Ar seq TCP packets only. Match if the TCP header sequence number field is set to diff --git a/sbin/ipfw/ipfw2.c b/sbin/ipfw/ipfw2.c index 3e73fa3b8736..4fd977edb91f 100644 --- a/sbin/ipfw/ipfw2.c +++ b/sbin/ipfw/ipfw2.c @@ -338,6 +338,7 @@ static struct _s_x rule_options[] = { { "tcpdatalen", TOK_TCPDATALEN }, { "tcpflags", TOK_TCPFLAGS }, { "tcpflgs", TOK_TCPFLAGS }, + { "tcpmss", TOK_TCPMSS }, { "tcpoptions", TOK_TCPOPTS }, { "tcpopts", TOK_TCPOPTS }, { "tcpseq", TOK_TCPSEQ }, @@ -881,6 +882,7 @@ static struct _s_x _port_name[] = { {"ipttl", O_IPTTL}, {"mac-type", O_MAC_TYPE}, {"tcpdatalen", O_TCPDATALEN}, + {"tcpmss", O_TCPMSS}, {"tcpwin", O_TCPWIN}, {"tagged", O_TAGGED}, {NULL, 0} @@ -1588,6 +1590,7 @@ print_instruction(struct buf_pr *bp, const struct format_opts *fo, case O_IPTTL: case O_IPLEN: case O_TCPDATALEN: + case O_TCPMSS: case O_TCPWIN: if (F_LEN(cmd) == 1) { switch (cmd->opcode) { @@ -1603,6 +1606,9 @@ print_instruction(struct buf_pr *bp, const struct format_opts *fo, case O_TCPDATALEN: s = "tcpdatalen"; break; + case O_TCPMSS: + s = "tcpmss"; + break; case O_TCPWIN: s = "tcpwin"; break; @@ -4709,14 +4715,18 @@ compile_rule(char *av[], uint32_t *rbuf, int *rbufsize, struct tidx *tstate) av++; break; + case TOK_TCPMSS: case TOK_TCPWIN: - NEED1("tcpwin requires length"); + NEED1("tcpmss/tcpwin requires size"); if (strpbrk(*av, "-,")) { - if (!add_ports(cmd, *av, 0, O_TCPWIN, cblen)) - errx(EX_DATAERR, "invalid tcpwin len %s", *av); + if (add_ports(cmd, *av, 0, + i == TOK_TCPWIN ? O_TCPWIN : O_TCPMSS, + cblen) == NULL) + errx(EX_DATAERR, "invalid %s size %s", + s, *av); } else - fill_cmd(cmd, O_TCPWIN, 0, - strtoul(*av, NULL, 0)); + fill_cmd(cmd, i == TOK_TCPWIN ? O_TCPWIN : + O_TCPMSS, 0, strtoul(*av, NULL, 0)); av++; break; diff --git a/sbin/ipfw/ipfw2.h b/sbin/ipfw/ipfw2.h index e6c209d65bab..215416eecc8a 100644 --- a/sbin/ipfw/ipfw2.h +++ b/sbin/ipfw/ipfw2.h @@ -151,6 +151,7 @@ enum tokens { TOK_TCPOPTS, TOK_TCPSEQ, TOK_TCPACK, + TOK_TCPMSS, TOK_TCPWIN, TOK_ICMPTYPES, TOK_MAC, diff --git a/sys/netinet/ip_fw.h b/sys/netinet/ip_fw.h index de0cc29db1d2..7a01c82ba58b 100644 --- a/sys/netinet/ip_fw.h +++ b/sys/netinet/ip_fw.h @@ -293,6 +293,7 @@ enum ipfw_opcodes { /* arguments (4 byte each) */ O_EXTERNAL_DATA, /* variable length data */ O_SKIP_ACTION, /* none */ + O_TCPMSS, /* arg1=MSS value */ O_LAST_OPCODE /* not an opcode! */ }; diff --git a/sys/netpfil/ipfw/ip_fw2.c b/sys/netpfil/ipfw/ip_fw2.c index a4a3830132eb..6796ad81611d 100644 --- a/sys/netpfil/ipfw/ip_fw2.c +++ b/sys/netpfil/ipfw/ip_fw2.c @@ -331,10 +331,10 @@ ipopts_match(struct ip *ip, ipfw_insn *cmd) } static int -tcpopts_match(struct tcphdr *tcp, ipfw_insn *cmd) +tcpopts_parse(struct tcphdr *tcp, uint16_t *mss) { - int optlen, bits = 0; u_char *cp = (u_char *)(tcp + 1); + int optlen, bits = 0; int x = (tcp->th_off << 2) - sizeof(struct tcphdr); for (; x > 0; x -= optlen, cp += optlen) { @@ -350,12 +350,13 @@ tcpopts_match(struct tcphdr *tcp, ipfw_insn *cmd) } switch (opt) { - default: break; case TCPOPT_MAXSEG: bits |= IP_FW_TCPOPT_MSS; + if (mss != NULL) + *mss = be16dec(cp + 2); break; case TCPOPT_WINDOW: @@ -370,10 +371,16 @@ tcpopts_match(struct tcphdr *tcp, ipfw_insn *cmd) case TCPOPT_TIMESTAMP: bits |= IP_FW_TCPOPT_TS; break; - } } - return (flags_match(cmd, bits)); + return (bits); +} + +static int +tcpopts_match(struct tcphdr *tcp, ipfw_insn *cmd) +{ + + return (flags_match(cmd, tcpopts_parse(tcp, NULL))); } static int @@ -2316,6 +2323,31 @@ do { \ TCP(ulp)->th_ack); break; + case O_TCPMSS: + if (proto == IPPROTO_TCP && + (args->f_id._flags & TH_SYN) != 0 && + ulp != NULL) { + uint16_t mss, *p; + int i; + + PULLUP_LEN(hlen, ulp, + (TCP(ulp)->th_off << 2)); + if ((tcpopts_parse(TCP(ulp), &mss) & + IP_FW_TCPOPT_MSS) == 0) + break; + if (cmdlen == 1) { + match = (cmd->arg1 == mss); + break; + } + /* Otherwise we have ranges. */ + p = ((ipfw_insn_u16 *)cmd)->ports; + i = cmdlen - 1; + for (; !match && i > 0; i--, p += 2) + match = (mss >= p[0] && + mss <= p[1]); + } + break; + case O_TCPWIN: if (proto == IPPROTO_TCP && offset == 0) { uint16_t x; diff --git a/sys/netpfil/ipfw/ip_fw_sockopt.c b/sys/netpfil/ipfw/ip_fw_sockopt.c index a83e75447633..297b01ca7d3d 100644 --- a/sys/netpfil/ipfw/ip_fw_sockopt.c +++ b/sys/netpfil/ipfw/ip_fw_sockopt.c @@ -1176,7 +1176,9 @@ move_objects(struct ip_fw_chain *ch, ipfw_range_tlv *rt) } } return (c); -}/* +} + +/* * Changes set of given rule rannge @rt * with each other. * @@ -1907,6 +1909,7 @@ check_ipfw_rule_body(ipfw_insn *cmd, int cmd_len, struct rule_check_info *ci) case O_IPTTL: case O_IPLEN: case O_TCPDATALEN: + case O_TCPMSS: case O_TCPWIN: case O_TAGGED: if (cmdlen < 1 || cmdlen > 31)