/*- * Copyright (c) 2000 Ruslan Ermilov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static void correct_mss(struct tcphdr *, ssize_t len, u_int16_t); static int if_mtu(const char *, u_long *); static void sigterm_handler(int); static void usage(void); static int both; static int verbose; char pidfilename[MAXPATHLEN]; /*- * We are in liberal position about MSS * (RFC 879, section 7). */ #define MAXMSS(mtu) (mtu - sizeof(struct ip) - sizeof(struct tcphdr)) int main(int argc, char *argv[]) { char pkt[IP_MAXPACKET]; ssize_t pktlen, hlen; struct ip *pip = (struct ip *)pkt; struct sockaddr_in sin; socklen_t sinlen; int s; int mflag; u_long mtu; u_int16_t maxmss; int rtsock; int ifindex; int pflag; u_short port; int ch; fd_set fdset; mflag = pflag = 0; port = 0; /* XXX gcc -Wuninitialized */ ifindex = 0; rtsock = -1; while ((ch = getopt(argc, argv, "bi:m:p:v")) != -1) switch (ch) { case 'b': both = 1; break; case 'i': if (!(ifindex = if_mtu(optarg, &mtu))) { errx(1, "unknown interface %s", optarg); /* NOTREACHED */ } snprintf(pidfilename, sizeof pidfilename, "%stcpmssd.%s.pid", _PATH_VARRUN, optarg); break; case 'm': if ((mtu = atoi(optarg)) < 68) { errx(1, "invalid MTU value"); /* NOTREACHED */ } mflag++; break; case 'p': port = htons(atoi(optarg)); pflag++; break; case 'v': verbose = 1; break; case '?': default: usage(); /* NOTREACHED */ } argc -= optind; argv += optind; if (!(!mflag - !ifindex) || !pflag || argc) { usage(); /* NOTREACHED */ } if ((s = socket(PF_INET, SOCK_RAW, IPPROTO_DIVERT)) == -1) { err(1, "can't create divert socket"); /* NOTREACHED */ } bzero(&sin, sizeof(sin)); sin.sin_family = PF_INET; sin.sin_addr.s_addr = INADDR_ANY; sin.sin_port = port; if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) == -1) { err(1, "can't bind divert socket"); /* NOTREACHED */ } if (ifindex) if ((rtsock = socket(PF_ROUTE, SOCK_RAW, 0)) == -1) { err(1, "can't create routing socket"); /* NOTREACHED */ } maxmss = MAXMSS(mtu); if (verbose) fprintf(stderr, "Maximum MSS: %u\n", maxmss); if (!verbose) if (daemon(0, 0) == -1) { err(1, "can't daemonize"); /* NOTREACHED */ } if (ifindex) { FILE *pidfile; pidfile = fopen(pidfilename, "w"); if (pidfile != NULL) { fprintf(pidfile, "%d\n", (int)getpid()); fclose(pidfile); if (signal(SIGTERM, sigterm_handler) == SIG_ERR) { err(1, "can't install SIGTERM handler"); /* NOTREACHED */ } } } while (1) { FD_ZERO(&fdset); FD_SET(s, &fdset); if (rtsock != -1) FD_SET(rtsock, &fdset); if (select(32, &fdset, (fd_set *)NULL, (fd_set *)NULL, (struct timeval *)NULL) == -1) { warn("select failed"); continue; } if (rtsock != -1 && FD_ISSET(rtsock, &fdset)) { struct if_msghdr ifm; if ((pktlen = read(rtsock, &ifm, sizeof(ifm))) == -1) { warn("read from routing socket failed"); continue; } if (ifm.ifm_version != RTM_VERSION) { if (verbose) warnx("routing message version %d " "not understood", ifm.ifm_version); continue; } if (ifm.ifm_type != RTM_IFINFO || ifm.ifm_index != ifindex) continue; if (mtu != ifm.ifm_data.ifi_mtu) { mtu = ifm.ifm_data.ifi_mtu; maxmss = MAXMSS(mtu); if (verbose) fprintf(stderr, "Maximum MSS: %u\n", maxmss); } } if (FD_ISSET(s, &fdset)) { sinlen = sizeof(sin); if ((pktlen = recvfrom(s, pkt, sizeof(pkt), 0, (struct sockaddr *)&sin, &sinlen)) == -1) if (errno != EINTR) { warn("read from divert socket failed"); continue; } hlen = pip->ip_hl << 2; /*- * Check for MSS option only for outgoing * TCP packets with zero fragment offset * and correct total and header lengths. */ if ((both || sin.sin_addr.s_addr == INADDR_ANY) && pip->ip_p == IPPROTO_TCP && (ntohs(pip->ip_off) & IP_OFFMASK) == 0 && ntohs(pip->ip_len) == pktlen && hlen <= pktlen && pktlen - hlen >= sizeof(struct tcphdr)) correct_mss((struct tcphdr *)(pkt + hlen), pktlen - hlen, maxmss); if (sendto(s, pkt, pktlen, 0, (struct sockaddr *)&sin, sinlen) == -1) warn("write to divert socket failed"); } } } /*- * The following macro is used to update an * internet checksum. "acc" is a 32-bit * accumulation of all the changes to the * checksum (adding in old 16-bit words and * subtracting out new words), and "cksum" * is the checksum value to be updated. */ #define ADJUST_CHECKSUM(acc, cksum) { \ acc += cksum; \ if (acc < 0) { \ acc = -acc; \ acc = (acc >> 16) + (acc & 0xffff); \ acc += acc >> 16; \ cksum = (u_short) ~acc; \ } else { \ acc = (acc >> 16) + (acc & 0xffff); \ acc += acc >> 16; \ cksum = (u_short) acc; \ } \ } void correct_mss(struct tcphdr *tc, ssize_t pktlen, u_int16_t maxmss) { int hlen, olen, optlen; u_char *opt; u_int16_t *mss; int accumulate; hlen = tc->th_off << 2; /* Invalid header length or header without options. */ if (hlen <= sizeof(struct tcphdr) || hlen > pktlen) return; /* MSS option only allowed within SYN packets. */ if (!(tc->th_flags & TH_SYN)) return; for (olen = hlen - sizeof(struct tcphdr), opt = (u_char *)(tc + 1); olen > 0; olen -= optlen, opt += optlen) { if (*opt == TCPOPT_EOL) break; else if (*opt == TCPOPT_NOP) optlen = 1; else { optlen = *(opt + 1); if (optlen <= 0 || optlen > olen) break; if (*opt == TCPOPT_MAXSEG) { if (optlen != TCPOLEN_MAXSEG) continue; mss = (u_int16_t *)(opt + 2); if (ntohs(*mss) > maxmss) { if (verbose) fprintf(stderr, "MSS: %u -> %u\n", ntohs(*mss), maxmss); accumulate = *mss; *mss = htons(maxmss); accumulate -= *mss; ADJUST_CHECKSUM(accumulate, tc->th_sum); } } } } } static int if_mtu(const char *ifn, u_long *mtu) { size_t needed; int mib[6]; char *buf, *lim, *next; struct if_msghdr *ifm; struct sockaddr_dl *sdl; int ifindex; mib[0] = CTL_NET; mib[1] = PF_ROUTE; mib[2] = 0; mib[3] = AF_INET; /* Only IP addresses please. */ mib[4] = NET_RT_IFLIST; mib[5] = 0; /* List all interfaces. */ /* * Get interface data. */ if (sysctl(mib, 6, NULL, &needed, NULL, 0) == -1) err(1, "iflist-sysctl-estimate"); if ((buf = malloc(needed)) == NULL) errx(1, "malloc failed"); if (sysctl(mib, 6, buf, &needed, NULL, 0) == -1) err(1, "iflist-sysctl-get"); lim = buf + needed; /* * Loop through interfaces until one with * given name is found. This is done to * find correct interface index for routing * message processing. */ ifindex = 0; next = buf; while (next < lim) { ifm = (struct if_msghdr *)next; next += ifm->ifm_msglen; if (ifm->ifm_version != RTM_VERSION) { if (verbose) warnx("routing message version %d " "not understood", ifm->ifm_version); continue; } if (ifm->ifm_type == RTM_IFINFO) { sdl = (struct sockaddr_dl *)(ifm + 1); if (strlen(ifn) == sdl->sdl_nlen && strncmp(ifn, sdl->sdl_data, sdl->sdl_nlen) == 0) { *mtu = ifm->ifm_data.ifi_mtu; ifindex = ifm->ifm_index; break; } } } free(buf); return (ifindex); } static void sigterm_handler(int sig) { (void)unlink(pidfilename); exit(0); } void usage(void) { fprintf(stderr, "usage: tcpmssd [-v] -p port {-i iface | -m mtu}\n"); exit(1); }