1316435Sae/*- 2316435Sae * Copyright (c) 2017 Yandex LLC 3316435Sae * Copyright (c) 2017 Andrey V. Elsukov <ae@FreeBSD.org> 4316435Sae * All rights reserved. 5316435Sae * 6316435Sae * Redistribution and use in source and binary forms, with or without 7316435Sae * modification, are permitted provided that the following conditions 8316435Sae * are met: 9316435Sae * 10316435Sae * 1. Redistributions of source code must retain the above copyright 11316435Sae * notice, this list of conditions and the following disclaimer. 12316435Sae * 2. Redistributions in binary form must reproduce the above copyright 13316435Sae * notice, this list of conditions and the following disclaimer in the 14316435Sae * documentation and/or other materials provided with the distribution. 15316435Sae * 16316435Sae * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 17316435Sae * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 18316435Sae * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 19316435Sae * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 20316435Sae * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 21316435Sae * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22316435Sae * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23316435Sae * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24316435Sae * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 25316435Sae * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26316435Sae */ 27316435Sae 28316435Sae#include "opt_inet.h" 29316435Sae#include "opt_inet6.h" 30316435Sae 31316435Sae#include <sys/cdefs.h> 32316435Sae__FBSDID("$FreeBSD: stable/11/sys/netpfil/ipfw/pmod/tcpmod.c 337902 2018-08-16 09:42:09Z ae $"); 33316435Sae 34316435Sae#include <sys/param.h> 35316435Sae#include <sys/systm.h> 36316435Sae#include <sys/errno.h> 37316435Sae#include <sys/kernel.h> 38316435Sae#include <sys/mbuf.h> 39316435Sae#include <sys/module.h> 40316435Sae#include <sys/socket.h> 41316435Sae 42316435Sae#include <net/if.h> 43316435Sae#include <net/if_var.h> 44316435Sae#include <net/pfil.h> 45316435Sae#include <net/vnet.h> 46316435Sae 47316435Sae#include <netinet/in.h> 48316435Sae#include <netinet/ip.h> 49316435Sae#include <netinet/ip_var.h> 50316435Sae#include <netinet/tcp.h> 51316435Sae#include <netinet/ip_fw.h> 52316435Sae#include <netinet/ip6.h> 53316435Sae 54316435Sae#include <netpfil/ipfw/ip_fw_private.h> 55316435Sae#include <netpfil/ipfw/pmod/pmod.h> 56316435Sae 57316435Sae#include <machine/in_cksum.h> 58316435Sae 59316435Saestatic VNET_DEFINE(uint16_t, tcpmod_setmss_eid) = 0; 60316435Sae#define V_tcpmod_setmss_eid VNET(tcpmod_setmss_eid) 61316435Sae 62316435Saestatic int 63316435Saetcpmod_setmss(struct mbuf **mp, struct tcphdr *tcp, int tlen, uint16_t mss) 64316435Sae{ 65316435Sae struct mbuf *m; 66316435Sae u_char *cp; 67316435Sae int optlen, ret; 68316435Sae uint16_t oldmss, csum; 69316435Sae 70316435Sae m = *mp; 71316435Sae ret = IP_FW_DENY; 72316435Sae if (m->m_len < m->m_pkthdr.len) { 73316435Sae /* 74316435Sae * We shouldn't have any data, IP packet contains only 75316435Sae * TCP header with options. 76316435Sae */ 77316435Sae *mp = m = m_pullup(m, m->m_pkthdr.len); 78316435Sae if (m == NULL) 79316435Sae return (ret); 80316435Sae } 81316435Sae /* Parse TCP options. */ 82316435Sae for (tlen -= sizeof(struct tcphdr), cp = (u_char *)(tcp + 1); 83316435Sae tlen > 0; tlen -= optlen, cp += optlen) { 84316435Sae if (cp[0] == TCPOPT_EOL) 85316435Sae break; 86316435Sae if (cp[0] == TCPOPT_NOP) { 87316435Sae optlen = 1; 88316435Sae continue; 89316435Sae } 90316435Sae if (tlen < 2) 91316435Sae break; 92316435Sae optlen = cp[1]; 93316435Sae if (optlen < 2 || optlen > tlen) 94316435Sae break; 95316435Sae if (cp[0] == TCPOPT_MAXSEG) { 96316435Sae if (optlen != TCPOLEN_MAXSEG) 97316435Sae break; 98316435Sae ret = 0; /* report success */ 99316435Sae bcopy(cp + 2, &oldmss, sizeof(oldmss)); 100316435Sae /* Do not update lower MSS value */ 101337902Sae if (ntohs(oldmss) <= ntohs(mss)) 102316435Sae break; 103316435Sae bcopy(&mss, cp + 2, sizeof(mss)); 104316435Sae /* Update checksum if it is not delayed. */ 105316435Sae if ((m->m_pkthdr.csum_flags & 106316435Sae (CSUM_TCP | CSUM_TCP_IPV6)) == 0) { 107316435Sae bcopy(&tcp->th_sum, &csum, sizeof(csum)); 108316435Sae csum = cksum_adjust(csum, oldmss, mss); 109316435Sae bcopy(&csum, &tcp->th_sum, sizeof(csum)); 110316435Sae } 111316435Sae break; 112316435Sae } 113316435Sae } 114316435Sae 115316435Sae return (ret); 116316435Sae} 117316435Sae 118316435Sae#ifdef INET6 119316435Saestatic int 120316435Saetcpmod_ipv6_setmss(struct mbuf **mp, uint16_t mss) 121316435Sae{ 122316435Sae struct ip6_hdr *ip6; 123316435Sae struct ip6_hbh *hbh; 124316435Sae struct tcphdr *tcp; 125316435Sae int hlen, plen, proto; 126316435Sae 127316435Sae ip6 = mtod(*mp, struct ip6_hdr *); 128316435Sae hlen = sizeof(*ip6); 129316435Sae proto = ip6->ip6_nxt; 130316435Sae /* 131316435Sae * Skip IPv6 extension headers and get the TCP header. 132316435Sae * ipfw_chk() has already done this work. So we are sure that 133316435Sae * we will not do an access to the out of bounds. For this 134316435Sae * reason we skip some checks here. 135316435Sae */ 136316435Sae while (proto == IPPROTO_HOPOPTS || proto == IPPROTO_ROUTING || 137316435Sae proto == IPPROTO_DSTOPTS) { 138316435Sae hbh = mtodo(*mp, hlen); 139316435Sae proto = hbh->ip6h_nxt; 140320593Sae hlen += (hbh->ip6h_len + 1) << 3; 141316435Sae } 142316435Sae tcp = mtodo(*mp, hlen); 143316435Sae plen = (*mp)->m_pkthdr.len - hlen; 144316435Sae hlen = tcp->th_off << 2; 145316435Sae /* We must have TCP options and enough data in a packet. */ 146316435Sae if (hlen <= sizeof(struct tcphdr) || hlen > plen) 147316435Sae return (IP_FW_DENY); 148316435Sae return (tcpmod_setmss(mp, tcp, hlen, mss)); 149316435Sae} 150316435Sae#endif /* INET6 */ 151316435Sae 152316435Sae#ifdef INET 153316435Saestatic int 154316435Saetcpmod_ipv4_setmss(struct mbuf **mp, uint16_t mss) 155316435Sae{ 156316435Sae struct tcphdr *tcp; 157316435Sae struct ip *ip; 158316435Sae int hlen, plen; 159316435Sae 160316435Sae ip = mtod(*mp, struct ip *); 161316435Sae hlen = ip->ip_hl << 2; 162316435Sae tcp = mtodo(*mp, hlen); 163316435Sae plen = (*mp)->m_pkthdr.len - hlen; 164316435Sae hlen = tcp->th_off << 2; 165316435Sae /* We must have TCP options and enough data in a packet. */ 166316435Sae if (hlen <= sizeof(struct tcphdr) || hlen > plen) 167316435Sae return (IP_FW_DENY); 168316435Sae return (tcpmod_setmss(mp, tcp, hlen, mss)); 169316435Sae} 170316435Sae#endif /* INET */ 171316435Sae 172316435Sae/* 173316435Sae * ipfw external action handler. 174316435Sae */ 175316435Saestatic int 176316435Saeipfw_tcpmod(struct ip_fw_chain *chain, struct ip_fw_args *args, 177316435Sae ipfw_insn *cmd, int *done) 178316435Sae{ 179316435Sae ipfw_insn *icmd; 180316435Sae int ret; 181316435Sae 182316435Sae *done = 0; /* try next rule if not matched */ 183316435Sae ret = IP_FW_DENY; 184316435Sae icmd = cmd + 1; 185316435Sae if (cmd->opcode != O_EXTERNAL_ACTION || 186316435Sae cmd->arg1 != V_tcpmod_setmss_eid || 187316435Sae icmd->opcode != O_EXTERNAL_DATA || 188316435Sae icmd->len != F_INSN_SIZE(ipfw_insn)) 189316435Sae return (ret); 190316435Sae 191316435Sae /* 192316435Sae * NOTE: ipfw_chk() can set f_id.proto from IPv6 fragment header, 193316435Sae * but f_id._flags can be filled only from real TCP header. 194316435Sae * 195316435Sae * NOTE: ipfw_chk() drops very short packets in the PULLUP_TO() 196316435Sae * macro. But we need to check that mbuf is contiguous more than 197316435Sae * IP+IP_options/IP_extensions+tcphdr length, because TCP header 198316435Sae * must have TCP options, and ipfw_chk() does PULLUP_TO() size of 199316435Sae * struct tcphdr. 200316435Sae * 201316435Sae * NOTE: we require only the presence of SYN flag. User should 202316435Sae * properly configure the rule to select the direction of packets, 203316435Sae * that should be modified. 204316435Sae */ 205316435Sae if (args->f_id.proto != IPPROTO_TCP || 206316435Sae (args->f_id._flags & TH_SYN) == 0) 207316435Sae return (ret); 208316435Sae 209316435Sae switch (args->f_id.addr_type) { 210316435Sae#ifdef INET 211316435Sae case 4: 212316435Sae ret = tcpmod_ipv4_setmss(&args->m, htons(icmd->arg1)); 213316435Sae break; 214316435Sae#endif 215316435Sae#ifdef INET6 216316435Sae case 6: 217316435Sae ret = tcpmod_ipv6_setmss(&args->m, htons(icmd->arg1)); 218316435Sae break; 219316435Sae#endif 220316435Sae } 221316435Sae /* 222316435Sae * We return zero in both @ret and @done on success, and ipfw_chk() 223316435Sae * will update rule counters. Otherwise a packet will not be matched 224316435Sae * by rule. 225316435Sae */ 226316435Sae return (ret); 227316435Sae} 228316435Sae 229316435Saeint 230316435Saetcpmod_init(struct ip_fw_chain *ch, int first) 231316435Sae{ 232316435Sae 233316435Sae V_tcpmod_setmss_eid = ipfw_add_eaction(ch, ipfw_tcpmod, "tcp-setmss"); 234316435Sae if (V_tcpmod_setmss_eid == 0) 235316435Sae return (ENXIO); 236316435Sae return (0); 237316435Sae} 238316435Sae 239316435Saevoid 240316435Saetcpmod_uninit(struct ip_fw_chain *ch, int last) 241316435Sae{ 242316435Sae 243316435Sae ipfw_del_eaction(ch, V_tcpmod_setmss_eid); 244316435Sae V_tcpmod_setmss_eid = 0; 245316435Sae} 246316435Sae 247