/*
 *  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.
 *
 *  Copyright (C) 2002-2010 OpenVPN Technologies, Inc. <sales@openvpn.net>
 *
 *  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
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#elif defined(_MSC_VER)
#include "config-msvc.h"
#endif

#include "syshead.h"

#include "common.h"
#include "buffer.h"
#include "error.h"
#include "integer.h"
#include "mtu.h"
#include "options.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;
}

/*
 * 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_init_mssfix (struct frame *frame, const struct options *options)
{
  if (options->ce.mssfix)
    {
      frame_set_mtu_dynamic (frame, options->ce.mssfix, SET_MTU_UPPER_BOUND);
    }
}

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, sa_family_t proto_af)
{
  if (mtu_type >= 0)
    {
      switch (proto_af)
	{
#if defined(HAVE_SETSOCKOPT) && defined(IP_MTU_DISCOVER)
	case AF_INET:
	  if (setsockopt
	      (sd, IPPROTO_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);
	  break;
#endif
#if defined(HAVE_SETSOCKOPT) && defined(IPV6_MTU_DISCOVER)
	case AF_INET6:
	  if (setsockopt
	      (sd, IPPROTO_IPV6, IPV6_MTU_DISCOVER, &mtu_type, sizeof (mtu_type)))
	    msg (M_ERR, "Error setting IPV6_MTU_DISCOVER type=%d on TCP6/UDP6 socket",
		 mtu_type);
	  break;
#endif
	default:
	  msg (M_FATAL, MTUDISC_NOT_SUPPORTED_MSG);
	  break;
	}
    }
}

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

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, (void *) &on, sizeof (on)))
    msg (M_WARN | M_ERRNO,
	 "Note: enable extended error passing on TCP/UDP socket failed (IP_RECVERR)");
}

#endif
