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 __FBSDID("$FreeBSD$"); 33 34 #include <sys/param.h> 35 #include <sys/systm.h> 36 #include <sys/errno.h> 37 #include <sys/kernel.h> 38 #include <sys/mbuf.h> 39 #include <sys/module.h> 40 #include <sys/socket.h> 41 42 #include <net/if.h> 43 #include <net/if_var.h> 44 #include <net/pfil.h> 45 #include <net/vnet.h> 46 47 #include <netinet/in.h> 48 #include <netinet/ip.h> 49 #include <netinet/ip_var.h> 50 #include <netinet/tcp.h> 51 #include <netinet/ip_fw.h> 52 #include <netinet/ip6.h> 53 54 #include <netpfil/ipfw/ip_fw_private.h> 55 #include <netpfil/ipfw/pmod/pmod.h> 56 57 #include <machine/in_cksum.h> 58 59 static VNET_DEFINE(uint16_t, tcpmod_setmss_eid) = 0; 60 #define V_tcpmod_setmss_eid VNET(tcpmod_setmss_eid) 61 62 static int 63 tcpmod_setmss(struct mbuf **mp, struct tcphdr *tcp, int tlen, uint16_t mss) 64 { 65 struct mbuf *m; 66 u_char *cp; 67 int optlen, ret; 68 uint16_t oldmss, csum; 69 70 m = *mp; 71 ret = IP_FW_DENY; 72 if (m->m_len < m->m_pkthdr.len) { 73 /* 74 * We shouldn't have any data, IP packet contains only 75 * TCP header with options. 76 */ 77 *mp = m = m_pullup(m, m->m_pkthdr.len); 78 if (m == NULL) 79 return (ret); 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 (oldmss <= 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) 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)); 149 } 150 #endif /* INET6 */ 151 152 #ifdef INET 153 static int 154 tcpmod_ipv4_setmss(struct mbuf **mp, uint16_t mss) 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)); 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 + 1; 185 if (cmd->opcode != O_EXTERNAL_ACTION || 186 cmd->arg1 != 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 break; 214 #endif 215 #ifdef INET6 216 case 6: 217 ret = tcpmod_ipv6_setmss(&args->m, htons(icmd->arg1)); 218 break; 219 #endif 220 } 221 /* 222 * We return zero in both @ret and @done on success, and ipfw_chk() 223 * will update rule counters. Otherwise a packet will not be matched 224 * by rule. 225 */ 226 return (ret); 227 } 228 229 int 230 tcpmod_init(struct ip_fw_chain *ch, int first) 231 { 232 233 V_tcpmod_setmss_eid = ipfw_add_eaction(ch, ipfw_tcpmod, "tcp-setmss"); 234 if (V_tcpmod_setmss_eid == 0) 235 return (ENXIO); 236 return (0); 237 } 238 239 void 240 tcpmod_uninit(struct ip_fw_chain *ch, int last) 241 { 242 243 ipfw_del_eaction(ch, V_tcpmod_setmss_eid); 244 V_tcpmod_setmss_eid = 0; 245 } 246 247