mtu.c 7.48 KB
Newer Older
aymeric's avatar
aymeric committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
/*
mediastreamer2 library - modular sound and video processing and streaming
Copyright (C) 2006  Simon MORLAT (simon.morlat@linphone.org)

This program 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.

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; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/


/* mtu.c : discover the mtu automatically */

Simon Morlat's avatar
Simon Morlat committed
23

aymeric's avatar
aymeric committed
24 25
#include "mediastreamer2/mscommon.h"

26 27 28
#define UDP_HEADER_SIZE   8
#define IPV4_HEADER_SIZE 20
#define IPV6_HEADER_SIZE 40
aymeric's avatar
aymeric committed
29

aymeric's avatar
aymeric committed
30
#if defined(WIN32) && !defined(_WIN32_WCE)
aymeric's avatar
aymeric committed
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76

HINSTANCE m_IcmpInst = NULL;

typedef struct ip_option_information {
    UCHAR   Ttl;
    UCHAR   Tos;
    UCHAR   Flags;
    UCHAR   OptionsSize;
    PUCHAR  OptionsData;
} IP_OPTION_INFORMATION, * PIP_OPTION_INFORMATION;

typedef BOOL (WINAPI *ICMPCLOSEHANDLE)(HANDLE IcmpHandle);
typedef HANDLE (WINAPI *ICMPCREATEFILE)(VOID);
typedef DWORD (WINAPI *ICMPSENDECHO)(HANDLE IcmpHandle,ULONG DestinationAddress, LPVOID RequestData, WORD RequestSize, PIP_OPTION_INFORMATION RequestOptions, LPVOID ReplyBuffer, DWORD ReplySize, DWORD Timeout);

ICMPCLOSEHANDLE pIcmpCloseHandle = NULL;
ICMPCREATEFILE pIcmpCreateFile = NULL;
ICMPSENDECHO pIcmpSendEcho = NULL;

#define IP_FLAG_DF      0x2         // Don't fragment this packet.
#define IP_OPT_ROUTER_ALERT 0x94  // Router Alert Option

#define IP_STATUS_BASE              11000
#define IP_PACKET_TOO_BIG           (IP_STATUS_BASE + 9)
#define IP_REQ_TIMED_OUT            (IP_STATUS_BASE + 10)

static int mtus[] = {
  1500,   // Ethernet, Point-to-Point (default)
  1492,   // IEEE 802.3
  1006,   // SLIP, ARPANET
  576,    // X.25 Networks
  544,    // DEC IP Portal
  512,    // NETBIOS
  508,    // IEEE 802/Source-Rt Bridge, ARCNET
  296,    // Point-to-Point (low delay)
  68,     // Official minimum
  0
};

int ms_discover_mtu(const char *host)
{
  int i;

	struct addrinfo hints,*ai=NULL;
	char port[10];
  char ipaddr[INET6_ADDRSTRLEN];
77
  int family = PF_INET;
aymeric's avatar
aymeric committed
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
  int err;

  HANDLE hIcmp;
  unsigned long target_addr;

  struct ip_option_information ip_opts;
  unsigned char reply_buffer[10000];

  if (!m_IcmpInst)
	{
		m_IcmpInst = LoadLibrary("icmp.dll");
		if (m_IcmpInst)
		{
			pIcmpCloseHandle = (ICMPCLOSEHANDLE)GetProcAddress(m_IcmpInst, "IcmpCloseHandle");
			pIcmpCreateFile  = (ICMPCREATEFILE) GetProcAddress(m_IcmpInst, "IcmpCreateFile");
			pIcmpSendEcho =	   (ICMPSENDECHO)   GetProcAddress(m_IcmpInst, "IcmpSendEcho");
		}
	}

  hIcmp = pIcmpCreateFile();

99 100 101 102 103
	/* Try to get the address family of the host (PF_INET or PF_INET6). */
	memset(&hints, 0, sizeof(hints));
	hints.ai_family = PF_UNSPEC;
	hints.ai_flags = AI_NUMERICHOST;
	err = getaddrinfo(host, NULL, &hints, &ai);
Ghislain MARY's avatar
Ghislain MARY committed
104 105 106 107 108
	if ((err != 0) && (ai != NULL)) {
		family = ai->ai_family;
		freeaddrinfo(ai);
		ai = NULL;
	}
109

aymeric's avatar
aymeric committed
110
	memset(&hints,0,sizeof(hints));
111
	hints.ai_family = family;
aymeric's avatar
aymeric committed
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
	hints.ai_socktype = SOCK_DGRAM;
	
	snprintf(port,sizeof(port),"0");
	err=getaddrinfo(host,port,&hints,&ai);
	if (err!=0){
    pIcmpCloseHandle( hIcmp );
		ms_error("getaddrinfo(): error\n");
		return -1;
	}
  getnameinfo (ai->ai_addr, ai->ai_addrlen, ipaddr, sizeof (ipaddr), port,
               sizeof (port), NI_NUMERICHOST | NI_NUMERICSERV);
	freeaddrinfo(ai);

  target_addr=inet_addr(ipaddr);


  /* Prepare the IP options */
  memset(&ip_opts,0,sizeof(ip_opts));
  ip_opts.Ttl=30;
  ip_opts.Flags = IP_FLAG_DF | IP_OPT_ROUTER_ALERT;


  // ignore icmpbuff data contents 
  for (i=0;mtus[i]!=0;i++)
  {
    char icmpbuff[2048];
    char *icmp_data = icmpbuff;

    int status = -1;
    if (pIcmpSendEcho)
      status=pIcmpSendEcho(hIcmp,
                          target_addr,
                          (LPVOID)icmp_data,
                          mtus[i]-60, /* icmp_data_size */
                          &ip_opts,
                          reply_buffer,
                          sizeof(reply_buffer),
aymeric's avatar
aymeric committed
149
                          3000L); // 3 seconds
aymeric's avatar
aymeric committed
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
    if (status || GetLastError() == IP_REQ_TIMED_OUT)
    {
      pIcmpCloseHandle( hIcmp );
      return mtus[i];
    }
  }

  pIcmpCloseHandle( hIcmp );

  return -1;
}

#elif defined(__linux)

#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netdb.h>

#ifndef IP_MTU
#define IP_MTU 14
#endif

int ms_discover_mtu(const char *host){
	int sock;
	int err,mtu=0,new_mtu;
	socklen_t optlen;
	char port[10];
	struct addrinfo hints,*ai=NULL;
182
	int family = PF_INET;
aymeric's avatar
aymeric committed
183 184 185 186
	int rand_port;
	int retry=0;
	struct timeval tv;

187 188 189 190 191 192 193
	/* Try to get the address family of the host (PF_INET or PF_INET6). */
	memset(&hints, 0, sizeof(hints));
	hints.ai_family = PF_UNSPEC;
	hints.ai_flags = AI_NUMERICHOST;
	err = getaddrinfo(host, NULL, &hints, &ai);
	if (err >= 0) family = ai->ai_family;

aymeric's avatar
aymeric committed
194
	memset(&hints,0,sizeof(hints));
195
	hints.ai_family = family;
aymeric's avatar
aymeric committed
196 197 198 199 200 201 202 203 204 205 206 207
	hints.ai_socktype = SOCK_DGRAM;
	
	gettimeofday(&tv,NULL);	
	srandom(tv.tv_usec);
	rand_port=random() & 0xFFFF;
	if (rand_port<1000) rand_port+=1000;
	snprintf(port,sizeof(port),"%i",rand_port);
	err=getaddrinfo(host,port,&hints,&ai);
	if (err!=0){
		ms_error("getaddrinfo(): %s\n",gai_strerror(err));
		return -1;
	}
208
	sock=socket(family,SOCK_DGRAM,0);
209 210 211 212 213
	if(sock < 0)
	{
		ms_error("socket(): %s",strerror(errno));
		return sock;
	}
214
	mtu = (family == PF_INET6) ? IPV6_PMTUDISC_DO: IP_PMTUDISC_DO;
aymeric's avatar
aymeric committed
215
	optlen=sizeof(mtu);
216
	err=setsockopt(sock,(family == PF_INET6) ? IPPROTO_IPV6 : IPPROTO_IP,(family == PF_INET6) ? IPV6_MTU_DISCOVER : IP_MTU_DISCOVER,&mtu,optlen);
aymeric's avatar
aymeric committed
217 218
	if (err!=0){
		ms_error("setsockopt(): %s",strerror(errno));
Simon Morlat's avatar
Simon Morlat committed
219 220 221
		err = close(sock);
		if (err!=0)
			ms_error("close(): %s", strerror(errno));
aymeric's avatar
aymeric committed
222 223 224 225 226 227
		return -1;
	}
	err=connect(sock,ai->ai_addr,ai->ai_addrlen);
	freeaddrinfo(ai);
	if (err!=0){
		ms_error("connect(): %s",strerror(errno));
Simon Morlat's avatar
Simon Morlat committed
228 229 230
		err = close(sock);
		if (err !=0)
			ms_error("close(): %s", strerror(errno));
aymeric's avatar
aymeric committed
231 232
		return -1;
	}
233
	mtu=1500;
aymeric's avatar
aymeric committed
234
	do{
235
		int send_returned;
236
		int datasize = mtu - (UDP_HEADER_SIZE + ((family == PF_INET6) ? IPV6_HEADER_SIZE : IPV4_HEADER_SIZE));	/*minus IP+UDP overhead*/
237 238 239
		char *buf=ms_malloc0(datasize);

		send_returned = send(sock,buf,datasize,0);
Simon Morlat's avatar
Simon Morlat committed
240 241 242
		if (send_returned==-1){
			/*ignore*/
		}
243
		ms_free(buf);
Simon Morlat's avatar
Simon Morlat committed
244
		usleep(500000);/*wait for an icmp message come back */
245
		err=getsockopt(sock,(family == PF_INET6) ? IPPROTO_IPV6 : IPPROTO_IP,(family == PF_INET6) ? IPV6_MTU : IP_MTU,&new_mtu,&optlen);
aymeric's avatar
aymeric committed
246 247
		if (err!=0){
			ms_error("getsockopt(): %s",strerror(errno));
Simon Morlat's avatar
Simon Morlat committed
248 249 250
			err = close(sock);
			if (err!=0)
				ms_error("close(): %s", strerror(errno));
aymeric's avatar
aymeric committed
251 252 253 254 255 256 257 258 259 260
			return -1;
		}else{
			ms_message("Partial MTU discovered : %i",new_mtu);
			if (new_mtu==mtu) break;
			else mtu=new_mtu;
		}
		retry++;
	}while(retry<10);
	
	ms_message("mtu to %s is %i",host,mtu);
Simon Morlat's avatar
Simon Morlat committed
261 262 263 264

	err = close(sock);
	if (err!=0)
		ms_error("close() %s", strerror(errno));
aymeric's avatar
aymeric committed
265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284
	return mtu;
}

#else

int ms_discover_mtu(const char*host){
	ms_warning("mtu discovery not implemented.");
	return -1;
}

#endif


void ms_set_mtu(int mtu){
	/*60= IPv6+UDP+RTP overhead */
	if (mtu>60){
		if (mtu>1500) mtu=1500;/*limit to 1500, the mediastreamer2 buffer are not large enough anyway*/
		ms_set_payload_max_size(mtu-60);
	}else ms_set_payload_max_size(0);
}