diff --git a/sbin/ipfw/ipfw.8 b/sbin/ipfw/ipfw.8 index b7104a8b9cbd..13dde532cb0a 100644 --- a/sbin/ipfw/ipfw.8 +++ b/sbin/ipfw/ipfw.8 @@ -650,6 +650,25 @@ The supported IP options are: The absence of a particular option may be denoted with a .Ql ! . +.It Cm tcpoptions Ar spec +Match if the TCP header contains the comma separated list of +options specified in +.Ar spec . +The supported TCP options are: +.Pp +.Cm mss +(maximum segment size), +.Cm window +(tcp window advertisement), +.Cm sack +(selective ack), +.Cm ts +(rfc1323 timestamp) and +.Cm cc +(rfc1644 t/tcp connection count). +The absence of a particular option may be denoted +with a +.Ql ! . .It Cm established TCP packets only. Match packets that have the RST or ACK bits set. diff --git a/sbin/ipfw/ipfw.c b/sbin/ipfw/ipfw.c index 0c432039928f..75cb89386b19 100644 --- a/sbin/ipfw/ipfw.c +++ b/sbin/ipfw/ipfw.c @@ -437,7 +437,7 @@ show_ipfw(struct ip_fw *chain, int pcwidth, int bcwidth) #define PRINTFLG(x) {if (_flg_printed) printf(",");\ printf(x); _flg_printed = 1;} - printf(" tcpflg "); + printf(" tcpflags "); if (chain->fw_tcpf & IP_FW_TCPF_FIN) PRINTFLG("fin"); if (chain->fw_tcpnf & IP_FW_TCPF_FIN) PRINTFLG("!fin"); if (chain->fw_tcpf & IP_FW_TCPF_SYN) PRINTFLG("syn"); @@ -451,6 +451,24 @@ show_ipfw(struct ip_fw *chain, int pcwidth, int bcwidth) if (chain->fw_tcpf & IP_FW_TCPF_URG) PRINTFLG("urg"); if (chain->fw_tcpnf & IP_FW_TCPF_URG) PRINTFLG("!urg"); } + if (chain->fw_tcpopt || chain->fw_tcpnopt) { + int _opt_printed = 0; +#define PRINTTOPT(x) {if (_opt_printed) printf(",");\ + printf(x); _opt_printed = 1;} + + printf(" tcpoptions "); + if (chain->fw_tcpopt & IP_FW_TCPOPT_MSS) PRINTTOPT("mss"); + if (chain->fw_tcpnopt & IP_FW_TCPOPT_MSS) PRINTTOPT("!mss"); + if (chain->fw_tcpopt & IP_FW_TCPOPT_WINDOW) PRINTTOPT("window"); + if (chain->fw_tcpnopt & IP_FW_TCPOPT_WINDOW) PRINTTOPT("!window"); + if (chain->fw_tcpopt & IP_FW_TCPOPT_SACK) PRINTTOPT("sack"); + if (chain->fw_tcpnopt & IP_FW_TCPOPT_SACK) PRINTTOPT("!sack"); + if (chain->fw_tcpopt & IP_FW_TCPOPT_TS) PRINTTOPT("ts"); + if (chain->fw_tcpnopt & IP_FW_TCPOPT_TS) PRINTTOPT("!ts"); + if (chain->fw_tcpopt & IP_FW_TCPOPT_CC) PRINTTOPT("cc"); + if (chain->fw_tcpnopt & IP_FW_TCPOPT_CC) PRINTTOPT("!cc"); + } + if (chain->fw_flg & IP_FW_F_ICMPBIT) { int type_index; int first = 1; @@ -818,6 +836,7 @@ show_usage(const char *fmt, ...) " {established|setup}\n" " tcpflags [!]{syn|fin|rst|ack|psh|urg},...\n" " ipoptions [!]{ssrr|lsrr|rr|ts},...\n" +" tcpoptions [!]{mss|window|sack|ts|cc},...\n" " icmptypes {type[,type]}...\n" " pipeconfig:\n" " {bw|bandwidth} {bit/s|Kbit/s|Mbit/s|Bytes/s|KBytes/s|MBytes/s}\n" @@ -1025,9 +1044,7 @@ fill_port(cnt, ptr, off, arg) } static void -fill_tcpflag(set, reset, vp) - u_char *set, *reset; - char **vp; +fill_tcpflag(u_char *set, u_char *reset, char **vp) { char *p = *vp,*q; u_char *d; @@ -1066,6 +1083,45 @@ fill_tcpflag(set, reset, vp) } } +static void +fill_tcpopts(u_char *set, u_char *reset, char **vp) +{ + char *p = *vp,*q; + u_char *d; + + while (p && *p) { + struct tpcopts { + char * name; + u_char value; + } opts[] = { + { "mss", IP_FW_TCPOPT_MSS }, + { "window", IP_FW_TCPOPT_WINDOW }, + { "sack", IP_FW_TCPOPT_SACK }, + { "ts", IP_FW_TCPOPT_TS }, + { "cc", IP_FW_TCPOPT_CC }, + }; + int i; + + if (*p == '!') { + p++; + d = reset; + } else { + d = set; + } + q = strchr(p, ','); + if (q) + *q++ = '\0'; + for (i = 0; i < sizeof(opts) / sizeof(opts[0]); ++i) + if (!strncmp(p, opts[i].name, strlen(p))) { + *d |= opts[i].value; + break; + } + if (i == sizeof(opts) / sizeof(opts[0])) + show_usage("invalid tcp option ``%s''", p); + p = q; + } +} + static void fill_ipopt(u_char *set, u_char *reset, char **vp) { @@ -1839,7 +1895,7 @@ add(ac,av) rule.fw_tcpnf |= IP_FW_TCPF_ACK; av++; ac--; continue; } - if (!strncmp(*av,"tcpflags",strlen(*av))) { + if (!strncmp(*av,"tcpflags",strlen(*av)) || !strncmp(*av,"tcpflgs",strlen(*av))) { av++; ac--; if (!ac) show_usage("missing argument" @@ -1847,6 +1903,14 @@ add(ac,av) fill_tcpflag(&rule.fw_tcpf, &rule.fw_tcpnf, av); av++; ac--; continue; } + if (!strncmp(*av,"tcpoptions",strlen(*av)) || !strncmp(*av, "tcpopts",strlen(*av))) { + av++; ac--; + if (!ac) + show_usage("missing argument" + " for ``tcpflags''"); + fill_tcpopts(&rule.fw_tcpopt, &rule.fw_tcpnopt, av); + av++; ac--; continue; + } } if (rule.fw_prot == IPPROTO_ICMP) { if (!strncmp(*av,"icmptypes",strlen(*av))) { diff --git a/sys/netinet/ip_fw.c b/sys/netinet/ip_fw.c index b9245bd0d296..4ed2b0b2e0ff 100644 --- a/sys/netinet/ip_fw.c +++ b/sys/netinet/ip_fw.c @@ -353,6 +353,73 @@ ipopts_match(struct ip *ip, struct ip_fw *f) return 0; } +static int +tcpopts_match(struct tcphdr *tcp, struct ip_fw *f) +{ + register u_char *cp; + int opt, optlen, cnt; + u_char opts, nopts, nopts_sve; + + cp = (u_char *)(tcp + 1); + cnt = (tcp->th_off << 2) - sizeof (struct tcphdr); + opts = f->fw_tcpopt; + nopts = nopts_sve = f->fw_tcpnopt; + + for (; cnt > 0; cnt -= optlen, cp += optlen) { + opt = cp[0]; + if (opt == TCPOPT_EOL) + break; + if (opt == TCPOPT_NOP) + optlen = 1; + else { + optlen = cp[1]; + if (optlen <= 0) + break; + } + + + switch (opt) { + + default: + break; + + case TCPOPT_MAXSEG: + opts &= ~IP_FW_TCPOPT_MSS; + nopts &= ~IP_FW_TCPOPT_MSS; + break; + + case TCPOPT_WINDOW: + opts &= ~IP_FW_TCPOPT_WINDOW; + nopts &= ~IP_FW_TCPOPT_WINDOW; + break; + + case TCPOPT_SACK_PERMITTED: + case TCPOPT_SACK: + opts &= ~IP_FW_TCPOPT_SACK; + nopts &= ~IP_FW_TCPOPT_SACK; + break; + + case TCPOPT_TIMESTAMP: + opts &= ~IP_FW_TCPOPT_TS; + nopts &= ~IP_FW_TCPOPT_TS; + break; + + case TCPOPT_CC: + case TCPOPT_CCNEW: + case TCPOPT_CCECHO: + opts &= ~IP_FW_TCPOPT_CC; + nopts &= ~IP_FW_TCPOPT_CC; + break; + } + if (opts == nopts) + break; + } + if (opts == 0 && nopts == nopts_sve) + return 1; + else + return 0; +} + static __inline int iface_match(struct ifnet *ifp, union ip_fw_if *ifu, int byname) { @@ -1143,6 +1210,9 @@ ip_fw_chk(struct ip **pip, int hlen, break; } tcp = (struct tcphdr *) ((u_int32_t *)ip + ip->ip_hl); + + if (f->fw_tcpopt != f->fw_tcpnopt && !tcpopts_match(tcp, f)) + continue; if (f->fw_tcpf != f->fw_tcpnf && !tcpflg_match(tcp, f)) continue; goto check_ports; diff --git a/sys/netinet/ip_fw.h b/sys/netinet/ip_fw.h index 946762431492..78fd18db3f06 100644 --- a/sys/netinet/ip_fw.h +++ b/sys/netinet/ip_fw.h @@ -64,6 +64,7 @@ struct ip_fw { unsigned fw_icmptypes[IP_FW_ICMPTYPES_DIM]; /* ICMP types bitmap */ } fw_uar; u_char fw_ipopt,fw_ipnopt; /* IP options set/unset */ + u_char fw_tcpopt,fw_tcpnopt; /* TCP options set/unset */ u_char fw_tcpf,fw_tcpnf; /* TCP flags set/unset */ long timestamp; /* timestamp (tv_sec) of last match */ union ip_fw_if fw_in_if, fw_out_if; /* Incoming and outgoing interfaces */ @@ -229,6 +230,15 @@ struct ipfw_dyn_rule { #define IP_FW_IPOPT_RR 0x04 #define IP_FW_IPOPT_TS 0x08 +/* + * Definitions for TCP option names. + */ +#define IP_FW_TCPOPT_MSS 0x01 +#define IP_FW_TCPOPT_WINDOW 0x02 +#define IP_FW_TCPOPT_SACK 0x04 +#define IP_FW_TCPOPT_TS 0x08 +#define IP_FW_TCPOPT_CC 0x10 + /* * Definitions for TCP flags. */