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