mtu.c
6fbf66fa
 /*
  *  OpenVPN -- An application to securely tunnel IP networks
  *             over a single TCP/UDP port, with support for SSL/TLS-based
  *             session authentication and key exchange,
  *             packet encryption, packet authentication, and
  *             packet compression.
  *
1c0cc4ad
  *  Copyright (C) 2002-2008 Telethra, Inc. <sales@openvpn.net>
6fbf66fa
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License version 2
  *  as published by the Free Software Foundation.
  *
  *  This program 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 this program (see the file COPYING included with this
  *  distribution); if not, write to the Free Software Foundation, Inc.,
  *  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
 #include "syshead.h"
 
 #include "common.h"
 #include "buffer.h"
 #include "error.h"
 #include "integer.h"
 #include "mtu.h"
 
 #include "memdbg.h"
 
 /* allocate a buffer for socket or tun layer */
 void
 alloc_buf_sock_tun (struct buffer *buf,
 		    const struct frame *frame,
 		    const bool tuntap_buffer,
 		    const unsigned int align_mask)
 {
   /* allocate buffer for overlapped I/O */
   *buf = alloc_buf (BUF_SIZE (frame));
   ASSERT (buf_init (buf, FRAME_HEADROOM_ADJ (frame, align_mask)));
   buf->len = tuntap_buffer ? MAX_RW_SIZE_TUN (frame) : MAX_RW_SIZE_LINK (frame);
   ASSERT (buf_safe (buf, 0));
 }
 
 void
 frame_finalize (struct frame *frame,
 		bool link_mtu_defined,
 		int link_mtu,
 		bool tun_mtu_defined,
 		int tun_mtu)
 {
   /* Set link_mtu based on command line options */
   if (tun_mtu_defined)
     {
       ASSERT (!link_mtu_defined);
       frame->link_mtu = tun_mtu + TUN_LINK_DELTA (frame);
     }
   else
     {
       ASSERT (link_mtu_defined);
       frame->link_mtu = link_mtu;
     }
 
   if (TUN_MTU_SIZE (frame) < TUN_MTU_MIN)
     {
       msg (M_WARN, "TUN MTU value (%d) must be at least %d", TUN_MTU_SIZE (frame), TUN_MTU_MIN);
       frame_print (frame, M_FATAL, "MTU is too small");
     }
 
   frame->link_mtu_dynamic = frame->link_mtu;
 
   frame->extra_buffer += PAYLOAD_ALIGN;
 }
 
 /*
  * Set the tun MTU dynamically.
  */
 void
 frame_set_mtu_dynamic (struct frame *frame, int mtu, unsigned int flags)
 {
 
 #ifdef ENABLE_DEBUG
   const int orig_mtu = mtu;
   const int orig_link_mtu_dynamic = frame->link_mtu_dynamic;
 #endif
 
   ASSERT (mtu >= 0);
 
   if (flags & SET_MTU_TUN)
     mtu += TUN_LINK_DELTA (frame);
 
   if (!(flags & SET_MTU_UPPER_BOUND) || mtu < frame->link_mtu_dynamic)
     {
       frame->link_mtu_dynamic = constrain_int (
 	mtu,
 	EXPANDED_SIZE_MIN (frame),
 	EXPANDED_SIZE (frame));
     }
 
   dmsg (D_MTU_DEBUG, "MTU DYNAMIC mtu=%d, flags=%u, %d -> %d",
        orig_mtu,
        flags,
        orig_link_mtu_dynamic,
        frame->link_mtu_dynamic);
 }
 
 /*
  * Move extra_frame octets into extra_tun.  Used by fragmenting code
  * to adjust frame relative to its position in the buffer processing
  * queue.
  */
 void
 frame_subtract_extra (struct frame *frame, const struct frame *src)
 {
   frame->extra_frame -= src->extra_frame;
   frame->extra_tun   += src->extra_frame;
 }
 
 void
 frame_print (const struct frame *frame,
 	     int level,
 	     const char *prefix)
 {
   struct gc_arena gc = gc_new ();
   struct buffer out = alloc_buf_gc (256, &gc);
   if (prefix)
     buf_printf (&out, "%s ", prefix);
   buf_printf (&out, "[");
   buf_printf (&out, " L:%d", frame->link_mtu);
   buf_printf (&out, " D:%d", frame->link_mtu_dynamic);
   buf_printf (&out, " EF:%d", frame->extra_frame);
   buf_printf (&out, " EB:%d", frame->extra_buffer);
   buf_printf (&out, " ET:%d", frame->extra_tun);
   buf_printf (&out, " EL:%d", frame->extra_link);
   if (frame->align_flags && frame->align_adjust)
     buf_printf (&out, " AF:%u/%d", frame->align_flags, frame->align_adjust);
   buf_printf (&out, " ]");
 
   msg (level, "%s", out.data);
   gc_free (&gc);
 }
 
 #define MTUDISC_NOT_SUPPORTED_MSG "--mtu-disc is not supported on this OS"
 
 void
 set_mtu_discover_type (int sd, int mtu_type)
 {
   if (mtu_type >= 0)
     {
 #if defined(HAVE_SETSOCKOPT) && defined(SOL_IP) && defined(IP_MTU_DISCOVER)
       if (setsockopt
 	  (sd, SOL_IP, IP_MTU_DISCOVER, &mtu_type, sizeof (mtu_type)))
 	msg (M_ERR, "Error setting IP_MTU_DISCOVER type=%d on TCP/UDP socket",
 	     mtu_type);
 #else
       msg (M_FATAL, MTUDISC_NOT_SUPPORTED_MSG);
 #endif
     }
 }
 
 int
 translate_mtu_discover_type_name (const char *name)
 {
 #if defined(IP_PMTUDISC_DONT) && defined(IP_PMTUDISC_WANT) && defined(IP_PMTUDISC_DO)
   if (!strcmp (name, "yes"))
     return IP_PMTUDISC_DO;
   if (!strcmp (name, "maybe"))
     return IP_PMTUDISC_WANT;
   if (!strcmp (name, "no"))
     return IP_PMTUDISC_DONT;
   msg (M_FATAL,
        "invalid --mtu-disc type: '%s' -- valid types are 'yes', 'maybe', or 'no'",
        name);
 #else
   msg (M_FATAL, MTUDISC_NOT_SUPPORTED_MSG);
 #endif
   return -1;			/* NOTREACHED */
 }
 
 #if EXTENDED_SOCKET_ERROR_CAPABILITY
 
 /*
  *
  * The following code is adapted from tracepath
  * under the terms of the GPL.
  * Copyright (C) Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>.
  */
 
 struct probehdr
 {
   uint32_t ttl;
   struct timeval tv;
 };
 
 const char *
 format_extended_socket_error (int fd, int *mtu, struct gc_arena *gc)
 {
   int res;
   struct probehdr rcvbuf;
   struct iovec iov;
   struct msghdr msg;
   struct cmsghdr *cmsg;
   struct sock_extended_err *e;
   struct sockaddr_in addr;
   struct buffer out = alloc_buf_gc (256, gc);
   char *cbuf = (char *) gc_malloc (256, false, gc);
 
   *mtu = 0;
 
   while (true)
     {
       memset (&rcvbuf, -1, sizeof (rcvbuf));
       iov.iov_base = &rcvbuf;
       iov.iov_len = sizeof (rcvbuf);
       msg.msg_name = (uint8_t *) &addr;
       msg.msg_namelen = sizeof (addr);
       msg.msg_iov = &iov;
       msg.msg_iovlen = 1;
       msg.msg_flags = 0;
       msg.msg_control = cbuf;
       msg.msg_controllen = 256; /* size of cbuf */
 
       res = recvmsg (fd, &msg, MSG_ERRQUEUE);
       if (res < 0)
 	goto exit;
 
       e = NULL;
 
       for (cmsg = CMSG_FIRSTHDR (&msg); cmsg; cmsg = CMSG_NXTHDR (&msg, cmsg))
 	{
 	  if (cmsg->cmsg_level == SOL_IP)
 	    {
 	      if (cmsg->cmsg_type == IP_RECVERR)
 		{
 		  e = (struct sock_extended_err *) CMSG_DATA (cmsg);
 		}
 	      else
 		{
 		  buf_printf (&out ,"CMSG=%d|", cmsg->cmsg_type);
 		}
 	    }
 	}
       if (e == NULL)
 	{
 	  buf_printf (&out, "NO-INFO|");
 	  goto exit;
 	}
 
       switch (e->ee_errno)
 	{
 	case ETIMEDOUT:
 	  buf_printf (&out, "ETIMEDOUT|");
 	  break;
 	case EMSGSIZE:
 	  buf_printf (&out, "EMSGSIZE Path-MTU=%d|", e->ee_info);
 	  *mtu = e->ee_info;
 	  break;
 	case ECONNREFUSED:
 	  buf_printf (&out, "ECONNREFUSED|");
 	  break;
 	case EPROTO:
 	  buf_printf (&out, "EPROTO|");
 	  break;
 	case EHOSTUNREACH:
 	  buf_printf (&out, "EHOSTUNREACH|");
 	  break;
 	case ENETUNREACH:
 	  buf_printf (&out, "ENETUNREACH|");
 	  break;
 	case EACCES:
 	  buf_printf (&out, "EACCES|");
 	  break;
 	default:
 	  buf_printf (&out, "UNKNOWN|");
 	  break;
 	}
     }
 
  exit:
   buf_rmtail (&out, '|');
   return BSTR (&out);
 }
 
 void
 set_sock_extended_error_passing (int sd)
 {
   int on = 1;
   if (setsockopt (sd, SOL_IP, IP_RECVERR, &on, sizeof (on)))
     msg (M_WARN | M_ERRNO,
 	 "Note: enable extended error passing on TCP/UDP socket failed (IP_RECVERR)");
 }
 
 #endif