1 /*- 2 * Copyright (c) 2017 Yandex LLC 3 * Copyright (c) 2017 Andrey V. Elsukov <ae@FreeBSD.org> 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 19 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 21 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 25 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 */ 27 28 #include "opt_inet.h" 29 #include "opt_inet6.h" 30 31 #include <sys/param.h> 32 #include <sys/systm.h> 33 #include <sys/errno.h> 34 #include <sys/kernel.h> 35 #include <sys/mbuf.h> 36 #include <sys/module.h> 37 #include <sys/socket.h> 38 39 #include <net/if.h> 40 #include <net/if_var.h> 41 #include <net/pfil.h> 42 #include <net/vnet.h> 43 44 #include <netinet/in.h> 45 #include <netinet/ip.h> 46 #include <netinet/ip_var.h> 47 #include <netinet/tcp.h> 48 #include <netinet/ip_fw.h> 49 #include <netinet/ip6.h> 50 51 #include <netpfil/ipfw/ip_fw_private.h> 52 #include <netpfil/ipfw/pmod/pmod.h> 53 54 #include <machine/in_cksum.h> 55 56 VNET_DEFINE_STATIC(uint32_t, tcpmod_setmss_eid) = 0; 57 #define V_tcpmod_setmss_eid VNET(tcpmod_setmss_eid) 58 59 static int 60 tcpmod_setmss(struct mbuf **mp, struct tcphdr *tcp, int tlen, uint16_t mss, 61 int *done) 62 { 63 struct mbuf *m; 64 u_char *cp; 65 int optlen, ret; 66 uint16_t oldmss, csum; 67 68 m = *mp; 69 ret = IP_FW_DENY; 70 if (m->m_len < m->m_pkthdr.len) { 71 /* 72 * We shouldn't have any data, IP packet contains only 73 * TCP header with options. 74 */ 75 *mp = m = m_pullup(m, m->m_pkthdr.len); 76 if (m == NULL) { 77 *done = 1; 78 return (ret); 79 } 80 } 81 /* Parse TCP options. */ 82 for (tlen -= sizeof(struct tcphdr), cp = (u_char *)(tcp + 1); 83 tlen > 0; tlen -= optlen, cp += optlen) { 84 if (cp[0] == TCPOPT_EOL) 85 break; 86 if (cp[0] == TCPOPT_NOP) { 87 optlen = 1; 88 continue; 89 } 90 if (tlen < 2) 91 break; 92 optlen = cp[1]; 93 if (optlen < 2 || optlen > tlen) 94 break; 95 if (cp[0] == TCPOPT_MAXSEG) { 96 if (optlen != TCPOLEN_MAXSEG) 97 break; 98 ret = 0; /* report success */ 99 bcopy(cp + 2, &oldmss, sizeof(oldmss)); 100 /* Do not update lower MSS value */ 101 if (ntohs(oldmss) <= ntohs(mss)) 102 break; 103 bcopy(&mss, cp + 2, sizeof(mss)); 104 /* Update checksum if it is not delayed. */ 105 if ((m->m_pkthdr.csum_flags & 106 (CSUM_TCP | CSUM_TCP_IPV6)) == 0) { 107 bcopy(&tcp->th_sum, &csum, sizeof(csum)); 108 csum = cksum_adjust(csum, oldmss, mss); 109 bcopy(&csum, &tcp->th_sum, sizeof(csum)); 110 } 111 break; 112 } 113 } 114 115 return (ret); 116 } 117 118 #ifdef INET6 119 static int 120 tcpmod_ipv6_setmss(struct mbuf **mp, uint16_t mss, int *done) 121 { 122 struct ip6_hdr *ip6; 123 struct ip6_hbh *hbh; 124 struct tcphdr *tcp; 125 int hlen, plen, proto; 126 127 ip6 = mtod(*mp, struct ip6_hdr *); 128 hlen = sizeof(*ip6); 129 proto = ip6->ip6_nxt; 130 /* 131 * Skip IPv6 extension headers and get the TCP header. 132 * ipfw_chk() has already done this work. So we are sure that 133 * we will not do an access to the out of bounds. For this 134 * reason we skip some checks here. 135 */ 136 while (proto == IPPROTO_HOPOPTS || proto == IPPROTO_ROUTING || 137 proto == IPPROTO_DSTOPTS) { 138 hbh = mtodo(*mp, hlen); 139 proto = hbh->ip6h_nxt; 140 hlen += (hbh->ip6h_len + 1) << 3; 141 } 142 tcp = mtodo(*mp, hlen); 143 plen = (*mp)->m_pkthdr.len - hlen; 144 hlen = tcp->th_off << 2; 145 /* We must have TCP options and enough data in a packet. */ 146 if (hlen <= sizeof(struct tcphdr) || hlen > plen) 147 return (IP_FW_DENY); 148 return (tcpmod_setmss(mp, tcp, hlen, mss, done)); 149 } 150 #endif /* INET6 */ 151 152 #ifdef INET 153 static int 154 tcpmod_ipv4_setmss(struct mbuf **mp, uint16_t mss, int *done) 155 { 156 struct tcphdr *tcp; 157 struct ip *ip; 158 int hlen, plen; 159 160 ip = mtod(*mp, struct ip *); 161 hlen = ip->ip_hl << 2; 162 tcp = mtodo(*mp, hlen); 163 plen = (*mp)->m_pkthdr.len - hlen; 164 hlen = tcp->th_off << 2; 165 /* We must have TCP options and enough data in a packet. */ 166 if (hlen <= sizeof(struct tcphdr) || hlen > plen) 167 return (IP_FW_DENY); 168 return (tcpmod_setmss(mp, tcp, hlen, mss, done)); 169 } 170 #endif /* INET */ 171 172 /* 173 * ipfw external action handler. 174 */ 175 static int 176 ipfw_tcpmod(struct ip_fw_chain *chain, struct ip_fw_args *args, 177 ipfw_insn *cmd, int *done) 178 { 179 ipfw_insn *icmd; 180 int ret; 181 182 *done = 0; /* try next rule if not matched */ 183 ret = IP_FW_DENY; 184 icmd = cmd + F_LEN(cmd); 185 if (cmd->opcode != O_EXTERNAL_ACTION || 186 insntod(cmd, kidx)->kidx != V_tcpmod_setmss_eid || 187 icmd->opcode != O_EXTERNAL_DATA || 188 icmd->len != F_INSN_SIZE(ipfw_insn)) 189 return (ret); 190 191 /* 192 * NOTE: ipfw_chk() can set f_id.proto from IPv6 fragment header, 193 * but f_id._flags can be filled only from real TCP header. 194 * 195 * NOTE: ipfw_chk() drops very short packets in the PULLUP_TO() 196 * macro. But we need to check that mbuf is contiguous more than 197 * IP+IP_options/IP_extensions+tcphdr length, because TCP header 198 * must have TCP options, and ipfw_chk() does PULLUP_TO() size of 199 * struct tcphdr. 200 * 201 * NOTE: we require only the presence of SYN flag. User should 202 * properly configure the rule to select the direction of packets, 203 * that should be modified. 204 */ 205 if (args->f_id.proto != IPPROTO_TCP || 206 (args->f_id._flags & TH_SYN) == 0) 207 return (ret); 208 209 switch (args->f_id.addr_type) { 210 #ifdef INET 211 case 4: 212 ret = tcpmod_ipv4_setmss(&args->m, htons(icmd->arg1), 213 done); 214 break; 215 #endif 216 #ifdef INET6 217 case 6: 218 ret = tcpmod_ipv6_setmss(&args->m, htons(icmd->arg1), 219 done); 220 break; 221 #endif 222 } 223 /* 224 * We return zero in both @ret and @done on success, and ipfw_chk() 225 * will update rule counters. Otherwise a packet will not be matched 226 * by rule. We passed @done around above in case we hit a fatal error 227 * somewhere, we'll return non-zero but signal that rule processing 228 * cannot succeed. 229 */ 230 return (ret); 231 } 232 233 int 234 tcpmod_init(struct ip_fw_chain *ch, int first) 235 { 236 237 V_tcpmod_setmss_eid = ipfw_add_eaction(ch, ipfw_tcpmod, "tcp-setmss"); 238 if (V_tcpmod_setmss_eid == 0) 239 return (ENXIO); 240 return (0); 241 } 242 243 void 244 tcpmod_uninit(struct ip_fw_chain *ch, int last) 245 { 246 247 ipfw_del_eaction(ch, V_tcpmod_setmss_eid); 248 V_tcpmod_setmss_eid = 0; 249 } 250