1147231Sglebius/*- 2147231Sglebius * ng_tcpmss.c 3147231Sglebius * 4147231Sglebius * Copyright (c) 2004, Alexey Popov <lollypop@flexuser.ru> 5147231Sglebius * All rights reserved. 6147231Sglebius * 7147231Sglebius * Redistribution and use in source and binary forms, with or without 8147231Sglebius * modification, are permitted provided that the following conditions 9147231Sglebius * are met: 10147231Sglebius * 1. Redistributions of source code must retain the above copyright 11147231Sglebius * notice unmodified, this list of conditions, and the following 12147231Sglebius * disclaimer. 13147231Sglebius * 2. Redistributions in binary form must reproduce the above copyright 14147231Sglebius * notice, this list of conditions and the following disclaimer in the 15147231Sglebius * documentation and/or other materials provided with the distribution. 16147231Sglebius * 17147231Sglebius * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18147231Sglebius * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19147231Sglebius * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20147231Sglebius * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21147231Sglebius * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22147231Sglebius * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23147231Sglebius * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24147231Sglebius * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25147231Sglebius * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26147231Sglebius * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27147231Sglebius * SUCH DAMAGE. 28147231Sglebius * 29147231Sglebius * This software includes fragments of the following programs: 30147231Sglebius * tcpmssd Ruslan Ermilov <ru@FreeBSD.org> 31147231Sglebius * 32147231Sglebius * $FreeBSD$ 33147231Sglebius */ 34147231Sglebius 35147231Sglebius/* 36147231Sglebius * This node is netgraph tool for workaround of PMTUD problem. It acts 37147231Sglebius * like filter for IP packets. If configured, it reduces MSS of TCP SYN 38147231Sglebius * packets. 39147231Sglebius * 40147231Sglebius * Configuration can be done by sending NGM_TCPMSS_CONFIG message. The 41147231Sglebius * message sets filter for incoming packets on hook 'inHook'. Packet's 42147231Sglebius * TCP MSS field is lowered to 'maxMSS' parameter and resulting packet 43147231Sglebius * is sent to 'outHook'. 44147231Sglebius * 45147231Sglebius * XXX: statistics are updated not atomically, so they may broke on SMP. 46147231Sglebius */ 47147231Sglebius 48147231Sglebius#include <sys/param.h> 49147231Sglebius#include <sys/systm.h> 50206032Smav#include <sys/endian.h> 51147231Sglebius#include <sys/errno.h> 52147231Sglebius#include <sys/kernel.h> 53147231Sglebius#include <sys/malloc.h> 54147231Sglebius#include <sys/mbuf.h> 55147231Sglebius 56147231Sglebius#include <netinet/in.h> 57147231Sglebius#include <netinet/in_systm.h> 58147231Sglebius#include <netinet/ip.h> 59147231Sglebius#include <netinet/tcp.h> 60147231Sglebius 61147231Sglebius#include <netgraph/ng_message.h> 62147231Sglebius#include <netgraph/netgraph.h> 63147231Sglebius#include <netgraph/ng_parse.h> 64147231Sglebius#include <netgraph/ng_tcpmss.h> 65147231Sglebius 66147231Sglebius/* Per hook info. */ 67147231Sglebiustypedef struct { 68147231Sglebius hook_p outHook; 69147231Sglebius struct ng_tcpmss_hookstat stats; 70147231Sglebius} *hpriv_p; 71147231Sglebius 72147231Sglebius/* Netgraph methods. */ 73147231Sglebiusstatic ng_constructor_t ng_tcpmss_constructor; 74147231Sglebiusstatic ng_rcvmsg_t ng_tcpmss_rcvmsg; 75147231Sglebiusstatic ng_newhook_t ng_tcpmss_newhook; 76147231Sglebiusstatic ng_rcvdata_t ng_tcpmss_rcvdata; 77147231Sglebiusstatic ng_disconnect_t ng_tcpmss_disconnect; 78147231Sglebius 79147231Sglebiusstatic int correct_mss(struct tcphdr *, int, uint16_t, int); 80147231Sglebius 81147231Sglebius/* Parse type for struct ng_tcpmss_hookstat. */ 82147231Sglebiusstatic const struct ng_parse_struct_field ng_tcpmss_hookstat_type_fields[] 83147231Sglebius = NG_TCPMSS_HOOKSTAT_INFO; 84147231Sglebiusstatic const struct ng_parse_type ng_tcpmss_hookstat_type = { 85147231Sglebius &ng_parse_struct_type, 86147231Sglebius &ng_tcpmss_hookstat_type_fields 87147231Sglebius}; 88147231Sglebius 89147231Sglebius/* Parse type for struct ng_tcpmss_config. */ 90147231Sglebiusstatic const struct ng_parse_struct_field ng_tcpmss_config_type_fields[] 91147231Sglebius = NG_TCPMSS_CONFIG_INFO; 92147231Sglebiusstatic const struct ng_parse_type ng_tcpmss_config_type = { 93147231Sglebius &ng_parse_struct_type, 94147231Sglebius ng_tcpmss_config_type_fields 95147231Sglebius}; 96147231Sglebius 97147231Sglebius/* List of commands and how to convert arguments to/from ASCII. */ 98147231Sglebiusstatic const struct ng_cmdlist ng_tcpmss_cmds[] = { 99147231Sglebius { 100147231Sglebius NGM_TCPMSS_COOKIE, 101147231Sglebius NGM_TCPMSS_GET_STATS, 102147231Sglebius "getstats", 103147231Sglebius &ng_parse_hookbuf_type, 104147231Sglebius &ng_tcpmss_hookstat_type 105147231Sglebius }, 106147231Sglebius { 107147231Sglebius NGM_TCPMSS_COOKIE, 108147231Sglebius NGM_TCPMSS_CLR_STATS, 109147231Sglebius "clrstats", 110147231Sglebius &ng_parse_hookbuf_type, 111147231Sglebius NULL 112147231Sglebius }, 113147231Sglebius { 114147231Sglebius NGM_TCPMSS_COOKIE, 115147231Sglebius NGM_TCPMSS_GETCLR_STATS, 116147231Sglebius "getclrstats", 117147231Sglebius &ng_parse_hookbuf_type, 118147231Sglebius &ng_tcpmss_hookstat_type 119147231Sglebius }, 120147231Sglebius { 121147231Sglebius NGM_TCPMSS_COOKIE, 122147231Sglebius NGM_TCPMSS_CONFIG, 123147231Sglebius "config", 124147231Sglebius &ng_tcpmss_config_type, 125147231Sglebius NULL 126147231Sglebius }, 127147231Sglebius { 0 } 128147231Sglebius}; 129147231Sglebius 130147231Sglebius/* Netgraph type descriptor. */ 131147231Sglebiusstatic struct ng_type ng_tcpmss_typestruct = { 132147231Sglebius .version = NG_ABI_VERSION, 133147231Sglebius .name = NG_TCPMSS_NODE_TYPE, 134147231Sglebius .constructor = ng_tcpmss_constructor, 135147231Sglebius .rcvmsg = ng_tcpmss_rcvmsg, 136147231Sglebius .newhook = ng_tcpmss_newhook, 137147231Sglebius .rcvdata = ng_tcpmss_rcvdata, 138147231Sglebius .disconnect = ng_tcpmss_disconnect, 139147231Sglebius .cmdlist = ng_tcpmss_cmds, 140147231Sglebius}; 141147231Sglebius 142147231SglebiusNETGRAPH_INIT(tcpmss, &ng_tcpmss_typestruct); 143147231Sglebius 144147231Sglebius#define ERROUT(x) { error = (x); goto done; } 145147231Sglebius 146147231Sglebius/* 147147231Sglebius * Node constructor. No special actions required. 148147231Sglebius */ 149147231Sglebiusstatic int 150147231Sglebiusng_tcpmss_constructor(node_p node) 151147231Sglebius{ 152147231Sglebius return (0); 153147231Sglebius} 154147231Sglebius 155147231Sglebius/* 156147231Sglebius * Add a hook. Any unique name is OK. 157147231Sglebius */ 158147231Sglebiusstatic int 159147231Sglebiusng_tcpmss_newhook(node_p node, hook_p hook, const char *name) 160147231Sglebius{ 161147231Sglebius hpriv_p priv; 162147231Sglebius 163184205Sdes priv = malloc(sizeof(*priv), M_NETGRAPH, M_NOWAIT | M_ZERO); 164147231Sglebius if (priv == NULL) 165147231Sglebius return (ENOMEM); 166147231Sglebius 167147231Sglebius NG_HOOK_SET_PRIVATE(hook, priv); 168147231Sglebius 169147231Sglebius return (0); 170147231Sglebius} 171147231Sglebius 172147231Sglebius/* 173147231Sglebius * Receive a control message. 174147231Sglebius */ 175147231Sglebiusstatic int 176147231Sglebiusng_tcpmss_rcvmsg 177147231Sglebius(node_p node, item_p item, hook_p lasthook) 178147231Sglebius{ 179147231Sglebius struct ng_mesg *msg, *resp = NULL; 180147231Sglebius int error = 0; 181147231Sglebius 182147231Sglebius NGI_GET_MSG(item, msg); 183147231Sglebius 184147231Sglebius switch (msg->header.typecookie) { 185147231Sglebius case NGM_TCPMSS_COOKIE: 186147231Sglebius switch (msg->header.cmd) { 187147231Sglebius case NGM_TCPMSS_GET_STATS: 188147231Sglebius case NGM_TCPMSS_CLR_STATS: 189147231Sglebius case NGM_TCPMSS_GETCLR_STATS: 190147231Sglebius { 191147231Sglebius hook_p hook; 192147231Sglebius hpriv_p priv; 193147231Sglebius 194147231Sglebius /* Check that message is long enough. */ 195147231Sglebius if (msg->header.arglen != NG_HOOKSIZ) 196147231Sglebius ERROUT(EINVAL); 197147231Sglebius 198147231Sglebius /* Find this hook. */ 199147231Sglebius hook = ng_findhook(node, (char *)msg->data); 200147231Sglebius if (hook == NULL) 201147231Sglebius ERROUT(ENOENT); 202147231Sglebius 203147231Sglebius priv = NG_HOOK_PRIVATE(hook); 204147231Sglebius 205147231Sglebius /* Create response. */ 206147231Sglebius if (msg->header.cmd != NGM_TCPMSS_CLR_STATS) { 207147231Sglebius NG_MKRESPONSE(resp, msg, 208147231Sglebius sizeof(struct ng_tcpmss_hookstat), M_NOWAIT); 209147231Sglebius if (resp == NULL) 210147231Sglebius ERROUT(ENOMEM); 211147231Sglebius bcopy(&priv->stats, resp->data, 212147231Sglebius sizeof(struct ng_tcpmss_hookstat)); 213147231Sglebius } 214147231Sglebius 215147231Sglebius if (msg->header.cmd != NGM_TCPMSS_GET_STATS) 216147231Sglebius bzero(&priv->stats, 217147231Sglebius sizeof(struct ng_tcpmss_hookstat)); 218147231Sglebius break; 219147231Sglebius } 220147231Sglebius case NGM_TCPMSS_CONFIG: 221147231Sglebius { 222147231Sglebius struct ng_tcpmss_config *set; 223147231Sglebius hook_p in, out; 224147231Sglebius hpriv_p priv; 225147231Sglebius 226147231Sglebius /* Check that message is long enough. */ 227147231Sglebius if (msg->header.arglen != 228147231Sglebius sizeof(struct ng_tcpmss_config)) 229147231Sglebius ERROUT(EINVAL); 230147231Sglebius 231147231Sglebius set = (struct ng_tcpmss_config *)msg->data; 232147231Sglebius in = ng_findhook(node, set->inHook); 233147231Sglebius out = ng_findhook(node, set->outHook); 234147231Sglebius if (in == NULL || out == NULL) 235147231Sglebius ERROUT(ENOENT); 236147231Sglebius 237147231Sglebius /* Configure MSS hack. */ 238147231Sglebius priv = NG_HOOK_PRIVATE(in); 239147231Sglebius priv->outHook = out; 240147231Sglebius priv->stats.maxMSS = set->maxMSS; 241147231Sglebius 242147231Sglebius break; 243147231Sglebius } 244147231Sglebius default: 245147231Sglebius error = EINVAL; 246147231Sglebius break; 247147231Sglebius } 248147231Sglebius break; 249147231Sglebius default: 250147231Sglebius error = EINVAL; 251147231Sglebius break; 252147231Sglebius } 253147231Sglebius 254147231Sglebiusdone: 255147231Sglebius NG_RESPOND_MSG(error, node, item, resp); 256147231Sglebius NG_FREE_MSG(msg); 257147231Sglebius 258147231Sglebius return (error); 259147231Sglebius} 260147231Sglebius 261147231Sglebius/* 262147231Sglebius * Receive data on a hook, and hack MSS. 263147231Sglebius * 264147231Sglebius */ 265147231Sglebiusstatic int 266147231Sglebiusng_tcpmss_rcvdata(hook_p hook, item_p item) 267147231Sglebius{ 268147231Sglebius hpriv_p priv = NG_HOOK_PRIVATE(hook); 269147231Sglebius struct mbuf *m = NULL; 270147231Sglebius struct ip *ip; 271147231Sglebius struct tcphdr *tcp; 272147231Sglebius int iphlen, tcphlen, pktlen; 273147231Sglebius int pullup_len = 0; 274147231Sglebius int error = 0; 275147231Sglebius 276147231Sglebius /* Drop packets if filter is not configured on this hook. */ 277147231Sglebius if (priv->outHook == NULL) 278147231Sglebius goto done; 279147231Sglebius 280147231Sglebius NGI_GET_M(item, m); 281147231Sglebius 282147231Sglebius /* Update stats on incoming hook. */ 283147231Sglebius pktlen = m->m_pkthdr.len; 284147231Sglebius priv->stats.Octets += pktlen; 285147231Sglebius priv->stats.Packets++; 286147231Sglebius 287147231Sglebius /* Check whether we configured to fix MSS. */ 288147231Sglebius if (priv->stats.maxMSS == 0) 289147231Sglebius goto send; 290147231Sglebius 291147231Sglebius#define M_CHECK(length) do { \ 292147231Sglebius pullup_len += length; \ 293147248Sglebius if ((m)->m_pkthdr.len < pullup_len) \ 294147231Sglebius goto send; \ 295147248Sglebius if ((m)->m_len < pullup_len && \ 296147248Sglebius (((m) = m_pullup((m), pullup_len)) == NULL)) \ 297147231Sglebius ERROUT(ENOBUFS); \ 298147231Sglebius } while (0) 299147231Sglebius 300147231Sglebius /* Check mbuf packet size and arrange for IP header. */ 301147231Sglebius M_CHECK(sizeof(struct ip)); 302147231Sglebius ip = mtod(m, struct ip *); 303147231Sglebius 304147231Sglebius /* Check IP version. */ 305147231Sglebius if (ip->ip_v != IPVERSION) 306147231Sglebius ERROUT(EINVAL); 307147231Sglebius 308147231Sglebius /* Check IP header length. */ 309147231Sglebius iphlen = ip->ip_hl << 2; 310147231Sglebius if (iphlen < sizeof(struct ip) || iphlen > pktlen ) 311147231Sglebius ERROUT(EINVAL); 312147231Sglebius 313147231Sglebius /* Check if it is TCP. */ 314147231Sglebius if (!(ip->ip_p == IPPROTO_TCP)) 315147231Sglebius goto send; 316147231Sglebius 317147231Sglebius /* Check mbuf packet size and arrange for IP+TCP header */ 318147248Sglebius M_CHECK(iphlen - sizeof(struct ip) + sizeof(struct tcphdr)); 319166018Sglebius ip = mtod(m, struct ip *); 320147231Sglebius tcp = (struct tcphdr *)((caddr_t )ip + iphlen); 321147231Sglebius 322147231Sglebius /* Check TCP header length. */ 323147231Sglebius tcphlen = tcp->th_off << 2; 324147231Sglebius if (tcphlen < sizeof(struct tcphdr) || tcphlen > pktlen - iphlen) 325147231Sglebius ERROUT(EINVAL); 326147231Sglebius 327147231Sglebius /* Check SYN packet and has options. */ 328147231Sglebius if (!(tcp->th_flags & TH_SYN) || tcphlen == sizeof(struct tcphdr)) 329147231Sglebius goto send; 330147231Sglebius 331147231Sglebius /* Update SYN stats. */ 332147231Sglebius priv->stats.SYNPkts++; 333147231Sglebius 334147248Sglebius M_CHECK(tcphlen - sizeof(struct tcphdr)); 335166018Sglebius ip = mtod(m, struct ip *); 336166018Sglebius tcp = (struct tcphdr *)((caddr_t )ip + iphlen); 337147231Sglebius 338147231Sglebius#undef M_CHECK 339147231Sglebius 340147231Sglebius /* Fix MSS and update stats. */ 341147231Sglebius if (correct_mss(tcp, tcphlen, priv->stats.maxMSS, 342147231Sglebius m->m_pkthdr.csum_flags)) 343147231Sglebius priv->stats.FixedPkts++; 344147231Sglebius 345147231Sglebiussend: 346147231Sglebius /* Deliver frame out destination hook. */ 347147248Sglebius NG_FWD_NEW_DATA(error, item, priv->outHook, m); 348147248Sglebius 349147231Sglebius return (error); 350147231Sglebius 351147231Sglebiusdone: 352147248Sglebius NG_FREE_ITEM(item); 353147231Sglebius NG_FREE_M(m); 354147231Sglebius 355147231Sglebius return (error); 356147231Sglebius} 357147231Sglebius 358147231Sglebius/* 359147231Sglebius * Hook disconnection. 360147231Sglebius * We must check all hooks, since they may reference this one. 361147231Sglebius */ 362147231Sglebiusstatic int 363147231Sglebiusng_tcpmss_disconnect(hook_p hook) 364147231Sglebius{ 365147231Sglebius node_p node = NG_HOOK_NODE(hook); 366147231Sglebius hook_p hook2; 367147231Sglebius 368147231Sglebius LIST_FOREACH(hook2, &node->nd_hooks, hk_hooks) { 369147231Sglebius hpriv_p priv = NG_HOOK_PRIVATE(hook2); 370147231Sglebius 371147231Sglebius if (priv->outHook == hook) 372147231Sglebius priv->outHook = NULL; 373147231Sglebius } 374147231Sglebius 375184205Sdes free(NG_HOOK_PRIVATE(hook), M_NETGRAPH); 376156742Sglebius 377147231Sglebius if (NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) 378147231Sglebius ng_rmnode_self(NG_HOOK_NODE(hook)); 379147231Sglebius 380147231Sglebius return (0); 381147231Sglebius} 382147231Sglebius 383147231Sglebius/* 384147231Sglebius * Code from tcpmssd. 385147231Sglebius */ 386147231Sglebius 387147231Sglebius/*- 388147231Sglebius * The following macro is used to update an 389147231Sglebius * internet checksum. "acc" is a 32-bit 390147231Sglebius * accumulation of all the changes to the 391147231Sglebius * checksum (adding in old 16-bit words and 392147231Sglebius * subtracting out new words), and "cksum" 393147231Sglebius * is the checksum value to be updated. 394147231Sglebius */ 395147231Sglebius#define TCPMSS_ADJUST_CHECKSUM(acc, cksum) do { \ 396147231Sglebius acc += cksum; \ 397147231Sglebius if (acc < 0) { \ 398147231Sglebius acc = -acc; \ 399147231Sglebius acc = (acc >> 16) + (acc & 0xffff); \ 400147231Sglebius acc += acc >> 16; \ 401147231Sglebius cksum = (u_short) ~acc; \ 402147231Sglebius } else { \ 403147231Sglebius acc = (acc >> 16) + (acc & 0xffff); \ 404147231Sglebius acc += acc >> 16; \ 405147231Sglebius cksum = (u_short) acc; \ 406147231Sglebius } \ 407147231Sglebius} while (0); 408147231Sglebius 409147231Sglebiusstatic int 410147231Sglebiuscorrect_mss(struct tcphdr *tc, int hlen, uint16_t maxmss, int flags) 411147231Sglebius{ 412147231Sglebius int olen, optlen; 413147231Sglebius u_char *opt; 414147231Sglebius int accumulate; 415147231Sglebius int res = 0; 416206032Smav uint16_t sum; 417147231Sglebius 418147231Sglebius for (olen = hlen - sizeof(struct tcphdr), opt = (u_char *)(tc + 1); 419147231Sglebius olen > 0; olen -= optlen, opt += optlen) { 420147231Sglebius if (*opt == TCPOPT_EOL) 421147231Sglebius break; 422147231Sglebius else if (*opt == TCPOPT_NOP) 423147231Sglebius optlen = 1; 424147231Sglebius else { 425147231Sglebius optlen = *(opt + 1); 426147231Sglebius if (optlen <= 0 || optlen > olen) 427147231Sglebius break; 428147231Sglebius if (*opt == TCPOPT_MAXSEG) { 429147231Sglebius if (optlen != TCPOLEN_MAXSEG) 430147231Sglebius continue; 431206032Smav accumulate = be16dec(opt + 2); 432206032Smav if (accumulate > maxmss) { 433206032Smav if ((flags & CSUM_TCP) == 0) { 434206032Smav accumulate -= maxmss; 435206032Smav sum = be16dec(&tc->th_sum); 436206032Smav TCPMSS_ADJUST_CHECKSUM(accumulate, sum); 437206032Smav be16enc(&tc->th_sum, sum); 438206032Smav } 439206032Smav be16enc(opt + 2, maxmss); 440147231Sglebius res = 1; 441147231Sglebius } 442147231Sglebius } 443147231Sglebius } 444147231Sglebius } 445147231Sglebius return (res); 446147231Sglebius} 447