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(uint16_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 { 62 struct mbuf *m; 63 u_char *cp; 64 int optlen, ret; 65 uint16_t oldmss, csum; 66 67 m = *mp; 68 ret = IP_FW_DENY; 69 if (m->m_len < m->m_pkthdr.len) { 70 /* 71 * We shouldn't have any data, IP packet contains only 72 * TCP header with options. 73 */ 74 *mp = m = m_pullup(m, m->m_pkthdr.len); 75 if (m == NULL) 76 return (ret); 77 } 78 /* Parse TCP options. */ 79 for (tlen -= sizeof(struct tcphdr), cp = (u_char *)(tcp + 1); 80 tlen > 0; tlen -= optlen, cp += optlen) { 81 if (cp[0] == TCPOPT_EOL) 82 break; 83 if (cp[0] == TCPOPT_NOP) { 84 optlen = 1; 85 continue; 86 } 87 if (tlen < 2) 88 break; 89 optlen = cp[1]; 90 if (optlen < 2 || optlen > tlen) 91 break; 92 if (cp[0] == TCPOPT_MAXSEG) { 93 if (optlen != TCPOLEN_MAXSEG) 94 break; 95 ret = 0; /* report success */ 96 bcopy(cp + 2, &oldmss, sizeof(oldmss)); 97 /* Do not update lower MSS value */ 98 if (ntohs(oldmss) <= ntohs(mss)) 99 break; 100 bcopy(&mss, cp + 2, sizeof(mss)); 101 /* Update checksum if it is not delayed. */ 102 if ((m->m_pkthdr.csum_flags & 103 (CSUM_TCP | CSUM_TCP_IPV6)) == 0) { 104 bcopy(&tcp->th_sum, &csum, sizeof(csum)); 105 csum = cksum_adjust(csum, oldmss, mss); 106 bcopy(&csum, &tcp->th_sum, sizeof(csum)); 107 } 108 break; 109 } 110 } 111 112 return (ret); 113 } 114 115 #ifdef INET6 116 static int 117 tcpmod_ipv6_setmss(struct mbuf **mp, uint16_t mss) 118 { 119 struct ip6_hdr *ip6; 120 struct ip6_hbh *hbh; 121 struct tcphdr *tcp; 122 int hlen, plen, proto; 123 124 ip6 = mtod(*mp, struct ip6_hdr *); 125 hlen = sizeof(*ip6); 126 proto = ip6->ip6_nxt; 127 /* 128 * Skip IPv6 extension headers and get the TCP header. 129 * ipfw_chk() has already done this work. So we are sure that 130 * we will not do an access to the out of bounds. For this 131 * reason we skip some checks here. 132 */ 133 while (proto == IPPROTO_HOPOPTS || proto == IPPROTO_ROUTING || 134 proto == IPPROTO_DSTOPTS) { 135 hbh = mtodo(*mp, hlen); 136 proto = hbh->ip6h_nxt; 137 hlen += (hbh->ip6h_len + 1) << 3; 138 } 139 tcp = mtodo(*mp, hlen); 140 plen = (*mp)->m_pkthdr.len - hlen; 141 hlen = tcp->th_off << 2; 142 /* We must have TCP options and enough data in a packet. */ 143 if (hlen <= sizeof(struct tcphdr) || hlen > plen) 144 return (IP_FW_DENY); 145 return (tcpmod_setmss(mp, tcp, hlen, mss)); 146 } 147 #endif /* INET6 */ 148 149 #ifdef INET 150 static int 151 tcpmod_ipv4_setmss(struct mbuf **mp, uint16_t mss) 152 { 153 struct tcphdr *tcp; 154 struct ip *ip; 155 int hlen, plen; 156 157 ip = mtod(*mp, struct ip *); 158 hlen = ip->ip_hl << 2; 159 tcp = mtodo(*mp, hlen); 160 plen = (*mp)->m_pkthdr.len - hlen; 161 hlen = tcp->th_off << 2; 162 /* We must have TCP options and enough data in a packet. */ 163 if (hlen <= sizeof(struct tcphdr) || hlen > plen) 164 return (IP_FW_DENY); 165 return (tcpmod_setmss(mp, tcp, hlen, mss)); 166 } 167 #endif /* INET */ 168 169 /* 170 * ipfw external action handler. 171 */ 172 static int 173 ipfw_tcpmod(struct ip_fw_chain *chain, struct ip_fw_args *args, 174 ipfw_insn *cmd, int *done) 175 { 176 ipfw_insn *icmd; 177 int ret; 178 179 *done = 0; /* try next rule if not matched */ 180 ret = IP_FW_DENY; 181 icmd = cmd + 1; 182 if (cmd->opcode != O_EXTERNAL_ACTION || 183 cmd->arg1 != V_tcpmod_setmss_eid || 184 icmd->opcode != O_EXTERNAL_DATA || 185 icmd->len != F_INSN_SIZE(ipfw_insn)) 186 return (ret); 187 188 /* 189 * NOTE: ipfw_chk() can set f_id.proto from IPv6 fragment header, 190 * but f_id._flags can be filled only from real TCP header. 191 * 192 * NOTE: ipfw_chk() drops very short packets in the PULLUP_TO() 193 * macro. But we need to check that mbuf is contiguous more than 194 * IP+IP_options/IP_extensions+tcphdr length, because TCP header 195 * must have TCP options, and ipfw_chk() does PULLUP_TO() size of 196 * struct tcphdr. 197 * 198 * NOTE: we require only the presence of SYN flag. User should 199 * properly configure the rule to select the direction of packets, 200 * that should be modified. 201 */ 202 if (args->f_id.proto != IPPROTO_TCP || 203 (args->f_id._flags & TH_SYN) == 0) 204 return (ret); 205 206 switch (args->f_id.addr_type) { 207 #ifdef INET 208 case 4: 209 ret = tcpmod_ipv4_setmss(&args->m, htons(icmd->arg1)); 210 break; 211 #endif 212 #ifdef INET6 213 case 6: 214 ret = tcpmod_ipv6_setmss(&args->m, htons(icmd->arg1)); 215 break; 216 #endif 217 } 218 /* 219 * We return zero in both @ret and @done on success, and ipfw_chk() 220 * will update rule counters. Otherwise a packet will not be matched 221 * by rule. 222 */ 223 return (ret); 224 } 225 226 int 227 tcpmod_init(struct ip_fw_chain *ch, int first) 228 { 229 230 V_tcpmod_setmss_eid = ipfw_add_eaction(ch, ipfw_tcpmod, "tcp-setmss"); 231 if (V_tcpmod_setmss_eid == 0) 232 return (ENXIO); 233 return (0); 234 } 235 236 void 237 tcpmod_uninit(struct ip_fw_chain *ch, int last) 238 { 239 240 ipfw_del_eaction(ch, V_tcpmod_setmss_eid); 241 V_tcpmod_setmss_eid = 0; 242 } 243