+/* -*-c-*-
+ *
+ * Report MTU on path to specified host
+ *
+ * (c) 2008 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of Trivial IP Encryption (TrIPE).
+ *
+ * TrIPE is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * TrIPE is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with TrIPE; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "config.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include <mLib/dstr.h>
+#include <mLib/hex.h>
+#include <mLib/mdwopt.h>
+#include <mLib/quis.h>
+#include <mLib/report.h>
+#include <mLib/tv.h>
+
+/*----- Static variables --------------------------------------------------*/
+
+static unsigned char buf[65536];
+
+/*----- Utility functions -------------------------------------------------*/
+
+/* Fill buffer with a constant but pseudorandom string. Uses a simple
+ * LFSR.
+ */
+static void fillbuffer(unsigned char *p, size_t sz)
+{
+ unsigned int y = 0xbc20;
+ const unsigned char *l = p + sz;
+ int i;
+#define POLY 0x002d
+
+ while (p < l) {
+ *p++ = y & 0xff;
+ for (i = 0; i < 8; i++) {
+ if (!(y & 0x8000)) y <<= 1;
+ else y = (y << 1) ^ POLY;
+ }
+ }
+}
+
+/*----- Doing the actual job ----------------------------------------------*/
+
+#if defined(linux)
+
+#ifndef IP_MTU
+# define IP_MTU 14 /* Blech! */
+#endif
+
+static int pathmtu(struct sockaddr_in *sin, double to)
+{
+ int sk;
+ fd_set fd_in;
+ int mtu;
+ int i;
+ size_t sz;
+ struct timeval tv;
+
+ tv.tv_sec = to; tv.tv_usec = (to - tv.tv_sec) * 1000000;
+ if ((sk = socket(PF_INET, SOCK_DGRAM, 0)) < 0) goto fail_0;
+ i = IP_PMTUDISC_DO;
+ if (setsockopt(sk, SOL_IP, IP_MTU_DISCOVER, &i, sizeof(i)))
+ goto fail_1;
+ if (connect(sk, (struct sockaddr *)sin, sizeof(*sin))) goto fail_1;
+ for (;;) {
+ sz = sizeof(mtu);
+ if (getsockopt(sk, SOL_IP, IP_MTU, &mtu, &sz)) goto fail_1;
+ if (write(sk, buf, mtu - 28) < 0) goto fail_1;
+ FD_SET(sk, &fd_in);
+ if (select(sk + 1, &fd_in, 0, 0, &tv) < 0) goto fail_1;
+ if (!FD_ISSET(sk, &fd_in)) break;
+ if (read(sk, &i, 1) >= 0 ||
+ errno == ECONNREFUSED || errno == EHOSTUNREACH)
+ break;
+ if (errno != EMSGSIZE) goto fail_1;
+ }
+ close(sk);
+ return (mtu);
+
+fail_1:
+ close(sk);
+fail_0:
+ return (-1);
+}
+
+#else
+
+# error "path MTU discovery not implemented"
+
+#endif
+
+/*----- Help options ------------------------------------------------------*/
+
+static void version(FILE *fp)
+ { pquis(fp, "$, TrIPE version " VERSION "\n"); }
+
+static void usage(FILE *fp)
+ { pquis(fp, "Usage: $ [-t TIMEOUT] [-H HEADER] HOST [PORT]\n"); }
+
+static void help(FILE *fp)
+{
+ version(fp);
+ fputc('\n', fp);
+ usage(fp);
+ fputs("\
+\n\
+Options in full:\n\
+\n\
+-h, --help Show this help text.\n\
+-v, --version Show version number.\n\
+-u, --usage Show brief usage message.\n\
+\n\
+-t, --timeout=TIMEOUT Time to wait for reply, in seconds.\n\
+-H, --header=HEX Packet header, in hexadecimal.\n\
+", fp);
+}
+
+/*----- Main code ---------------------------------------------------------*/
+
+int main(int argc, char *argv[])
+{
+ struct sockaddr_in sin;
+ hex_ctx hc;
+ dstr d = DSTR_INIT;
+ size_t sz;
+ int i;
+ unsigned long u;
+ char *q;
+ struct hostent *h;
+ struct servent *s;
+ double to = 5.0;
+ unsigned f = 0;
+
+#define f_bogus 1u
+
+ ego(argv[0]);
+ fillbuffer(buf, sizeof(buf));
+ sin.sin_port = htons(7);
+
+ for (;;) {
+ static const struct option opts[] = {
+ { "help", 0, 0, 'h' },
+ { "version", 0, 0, 'v' },
+ { "usage", 0, 0, 'u' },
+ { "header", OPTF_ARGREQ, 0, 'H' },
+ { "timeout", OPTF_ARGREQ, 0, 't' },
+ { 0, 0, 0, 0 }
+ };
+
+ i = mdwopt(argc, argv, "hvu" "H:", opts, 0, 0, 0);
+ if (i < 0) break;
+ switch (i) {
+ case 'h': help(stdout); exit(0);
+ case 'v': version(stdout); exit(0);
+ case 'u': usage(stdout); exit(0);
+
+ case 'H':
+ DRESET(&d);
+ hex_init(&hc);
+ hex_decode(&hc, optarg, strlen(optarg), &d);
+ hex_decode(&hc, 0, 0, &d);
+ sz = d.len < sizeof(buf) ? d.len : sizeof(buf);
+ memcpy(buf, d.buf, sz);
+ break;
+
+ case 't':
+ errno = 0;
+ to = strtod(optarg, &q);
+ if (errno || *q) die(EXIT_FAILURE, "bad timeout");
+ break;
+
+ default:
+ f |= f_bogus;
+ break;
+ }
+ }
+ argv += optind; argc -= optind;
+ if ((f & f_bogus) || 1 > argc || argc > 2) {
+ usage(stderr);
+ exit(EXIT_FAILURE);
+ }
+
+ if ((h = gethostbyname(*argv)) == 0)
+ die(EXIT_FAILURE, "unknown host `%s': %s", *argv, hstrerror(h_errno));
+ if (h->h_addrtype != AF_INET)
+ die(EXIT_FAILURE, "unsupported address family for host `%s'", *argv);
+ memcpy(&sin.sin_addr, h->h_addr, sizeof(struct in_addr));
+ argv++; argc--;
+
+ if (*argv) {
+ errno = 0;
+ u = strtoul(*argv, &q, 0);
+ if (!errno && !*q)
+ sin.sin_port = htons(u);
+ else if ((s = getservbyname(*argv, "udp")) == 0)
+ die(EXIT_FAILURE, "unknown UDP service `%s'", *argv);
+ else
+ sin.sin_port = s->s_port;
+ }
+
+ sin.sin_family = AF_INET;
+ i = pathmtu(&sin, to);
+ if (i < 0)
+ die(EXIT_FAILURE, "failed to discover MTU: %s", strerror(errno));
+ printf("%d\n", i);
+ if (ferror(stdout) || fflush(stdout) || fclose(stdout))
+ die(EXIT_FAILURE, "failed to write result: %s", strerror(errno));
+ return (0);
+}
+
+/*----- That's all, folks -------------------------------------------------*/