Acceso Remoto Usando el UDP

Vicente González Ruiz

February 27, 2017

Contents

1 El emisor
2 El receptor

El UDP le añade una funcionalidad mínima al IP. Transmite paquetes de datos entre procesos1 , que por supuesto pueden estar en hosts diferentes (direccionamiento de procesos). En el contexto del UDP los paquetes de datos se llaman datagramas UDP. Por supuesto, ya que el IP no es fiable, el UDP tampoco lo es, ni tampoco trata de recuperarse de los posibles errores de transmisión. Finalmente, es interesante conocer que actualmente el ratio de errores en redes cableadas es muy bajo, aunque no tanto en las redes vía radio (WiFi, por ejemplo). Y que cuando los errores aparecen, casi siempre los paquetes son descartados (destruidos sin intento de recuperación) o por los routers (cuando el error se detecta a nivel del IP) o por los hosts (cuando se detecta a nivel de la capa de transporte).

1 El emisor

A continuación se presenta el código de un emisor de datos que utiliza el UDP. Los datos son leídos de la entrada estándar, enviados por la red, y escritos sobre la salida estándar (con esto conseguimos seguir disponiendo de los datos). Este emisor acepta los siguientes parámetros:

  1. El tamaño del payload del datagrama. El IP limita el tamaño de un datagrama IP a 64 Kbytes y, por tanto, el UDP tiene también este límite. Los payloads suelen tener un tamaño cercano a 1000 bytes porque de esta manera son suficientemente grandes como para mantener bajo el overhead producido por las cabeceras y suficientemente pequeños como para evitar la fragmentación producida por el IPv4 cuando los datagramas atravienan redes con MTUs (Maximun Transfer Unit) más pequeños.
  2. El host destino de los datagramas. Nótese que se puede usar el DNS.
  3. El puerto (proceso) destino de los datagramas. Tiene que ser un valor comprendido entre 1 y 65535, aunque típicamente será superior al 1024 (los primeros 1024 puertos se dedican a los servicios (procesos) “conocidos” que sólo pueden usarse con privilegios de administrador).
  4. El Time-To-Live (TTL). Indica el número máximo de saltos (pasos por router) que realizarán los datagramas. Este valor se utiliza para evitar que los datagramas lleguen demasiado lejos, y en general, para evitar datagramas eternos que estando mal enrrutados podrían consumir ancho de banda en Internet de forma innecesaria.

A continuación se muestra el programa udp_send.c, que puede descargarse de http://www.ace.ual.es/\~vruiz/redes_industriales/udp_send.c:

 
1/****************************************************************************** 
2 * udp_send.c -- Emisor de datos que usa el UDP. 
3 * 
4 * Envia una copia de la entrada estandar hacia un host usando el 
5 * protocolo UDP. La entrada es tambien copiada a la salida estandar. 
6 * 
7 * gse. 2010. 
8 *****************************************************************************/ 
9 
10/****************************************************************************** 
11 * 
12 * Ficheros cabecera. 
13 * 
14 *****************************************************************************/ 
15 
16/* Entrada y salida de streams buffereados. */ 
17#include <stdio.h> 
18 
19/* Biblioteca de funciones estandar (exit(), EXIT_SUCCESS, 
20   EXIT_FAILURE, ...) */ 
21#include <stdlib.h> 
22 
23/* Manipulacion de cadenas y movimiento de memoria (memset(), ...). */ 
24#include <string.h> 
25 
26/* Biblioteca estandar de funciones relacionadas con el sistema 
27   operativo Unix (read(), write(), getopt(), ...). */ 
28#include <unistd.h> 
29 
30/* Llamadas al sistema para manejo de descriptores de ficheros, 
31   sockets, etc. */ 
32#include <fcntl.h> 
33 
34/* Sockets. */ 
35/* Mas info en: 
36 * 
37 * http://www.csce.uark.edu/~aapon/courses/os/examples/concurrentserver.c 
38 * http://www.fortunecity.com/skyscraper/arpanet/6/cc.htm 
39 * http://beej.us/guide/bgnet/ 
40 */ 
41 
42/* Tipos de datos primitivos del sistema. */ 
43#include <sys/types.h> 
44 
45/* Sockets. */ 
46#include <sys/socket.h> 
47 
48/* Direcciones IP, opciones y definiciones. */ 
49#include <netinet/in.h> 
50 
51/* Servicio de resolucian de nombres. */ 
52#include <netdb.h> 
53 
54/* Signals (interrupciones). */ 
55#include <signal.h> 
56 
57/****************************************************************************** 
58 * 
59 * Definiciones. 
60 * 
61 *****************************************************************************/ 
62 
63/* Tama~no (en bytes) del payload de los datagramas. */ 
64#define PAYLOAD_SIZE 512 
65 
66/* Host destino de los datagramas. */ 
67#define RECEIVER_HOST "localhost" 
68 
69/* Puerto destino de los datagramas en el host destino. */ 
70#define RECEIVER_PORT 6666 
71 
72/* Time to live. */ 
73#define TTL 32 
74 
75/* Indica si el socket es bloqueante o no. Los sockets normalmente son 
76   bloqueantes, lo que signfifica que la llamada al sistema que 
77   realiza el envio de datos a traves de la red se bloquea mientras el 
78   hardware de red no pueda hacerse cargo de los datos, por ejemplo, 
79   porque todavia se esta transmitiendo un paquete anterior. Sin 
80   embargo, hay situaciones donde puede ser interesante que la llamada 
81   al sistema no sea bloqueante. En este caso, si "la red" no esta 
82   lista, simplemente la llamada retorna y es posible seguir 
83   realizando alguna otra tarea. Asi, por ejemplo, cuando transmitimos 
84   audio por la red, si la red no esta lista lo que experimenta el 
85   receptor son cortes, pero no pausas. */ 
86#define BLOCKING 1 
87 
88/****************************************************************************** 
89 * 
90 * Variable globales. 
91 * 
92 *****************************************************************************/ 
93 
94/****************************************************************************** 
95 * 
96 * Funciones. 
97 * 
98 *****************************************************************************/ 
99 
100/* Cuerpo principal del programa. */ 
101main(int argc, char *argv[]) { 
102  work(argc, argv); 
103  return EXIT_SUCCESS; 
104} 
105 
106work(int argc, char *argv[]) { 
107 
108  int payload_size = PAYLOAD_SIZE; 
109  char *receiver_host = RECEIVER_HOST; 
110  int receiver_port = RECEIVER_PORT; 
111  unsigned char ttl = TTL; 
112  int blocking = BLOCKING; 
113 
114  /* Manipulamos los parametros de entrada. */ 
115  int c; 
116  while ((c = getopt (argc, argv, "ns:h:p:t:H")) != -1) { 
117    switch (c) { 
118    case n: 
119      blocking = 0; 
120      break; 
121    case s: 
122      payload_size = atoi(optarg); 
123      break; 
124    case h: 
125      receiver_host = optarg; 
126      break; 
127    case p: 
128      receiver_port = atoi(optarg); 
129      break; 
130    case t: 
131      ttl = atoi(optarg); 
132      break; 
133    case H: 
134      fprintf(stderr, "Usage:send[OPTION...]\n"); 
135      fprintf(stderr, "\n"); 
136      fprintf(stderr, "-n(forsenddatawithoutblocking)\n"); 
137      fprintf(stderr, "-spayload_size\n"); 
138      fprintf(stderr, "-hreceiver_host\n"); 
139      fprintf(stderr, "-preceiver_port\n"); 
140      fprintf(stderr, "-tTTL\n"); 
141      fprintf(stderr, "\n"); 
142      fprintf(stderr, "CopiesthestdintothestdoutandaUDPsocket\n"); 
143      return; 
144    case ?: 
145      if (isprint (optopt)) 
146        fprintf (stderr, "Unknownoption‘-%c’.\n", optopt); 
147      else 
148        fprintf (stderr, 
149                 "Unknownoptioncharacter‘\\x%x’.\n", 
150                 optopt); 
151      return 1; 
152    default: 
153      abort (); 
154    } 
155  } 
156 
157  fprintf(stderr,"%s:payloadsize=\"%d\"\n", argv[0],payload_size); 
158  fprintf(stderr,"%s:receiverhost=\"%s\"\n", argv[0],receiver_host); 
159  fprintf(stderr,"%s:receiverport=\"%d\"\n", argv[0],receiver_port); 
160  fprintf(stderr,"%s:TTL=\"%d\"\n", argv[0],ttl); 
161  fprintf(stderr,"%s:blocking=", argv[0]); 
162  if(blocking) { 
163    fprintf(stderr,"yes\n"); 
164  } else { 
165    fprintf(stderr,"no\n"); 
166  } 
167 
168  /* Preguntamos al DNS por la dir IP destino. */ 
169  struct hostent *receiver_IP; 
170  if ((receiver_IP = gethostbyname(receiver_host)) == NULL) { 
171    perror("gethostbyname"); 
172    exit(EXIT_FAILURE); 
173  } 
174 
175  int sd; /* Socket descriptor */ { 
176 
177    /* Obtenemos el numero del protocolo. */ 
178    struct protoent *ptrp; /* Pointer to a protocol table entry. */ 
179    ptrp = getprotobyname("udp"); 
180    if(ptrp == NULL) { 
181      perror("getprotobyname"); 
182      exit(EXIT_FAILURE); 
183    } 
184 
185    /* Creamos el socket descriptor. */ 
186    sd = socket(AF_INET, SOCK_DGRAM, ptrp->p_proto); 
187    if(sd < 0) { 
188      perror("socket"); 
189      exit(EXIT_FAILURE); 
190    } 
191  } 
192 
193  if(!blocking) { 
194    fprintf(stderr,"%s:usingnon-blockingsocket...", argv[0]); 
195    /* Configuramos el socket no bloqueante. */ 
196    fcntl(sd, F_SETFL, O_NONBLOCK); 
197  } 
198 
199  /* Si pulsamos CRTL+C, el programa acaba ejecutando la funcion 
200     end(). */ 
201  void end() { 
202    fprintf(stderr,"%s:CTRL+Cdetected.Exiting...",argv[0]); 
203    close(sd); 
204    fprintf(stderr,"done\n"); 
205    exit(EXIT_SUCCESS); 
206  } 
207  /* Activamos la sen~al SIGINT. */ 
208  signal(SIGINT, end); 
209 
210  /* Especificamos el host y puerto destino de los datagramas. */ 
211  struct sockaddr_in socket_addr; { 
212    /* Borramos la estructura. */ 
213    memset((char  *)&socket_addr,0,sizeof(socket_addr)); 
214    /* Usaremos Internet. */ 
215    socket_addr.sin_family = AF_INET; 
216    /* IP destino de los datagramas */ 
217    socket_addr.sin_addr = *((struct in_addr *)receiver_IP->h_addr); 
218    /* Puerto destino de los datagramas */ 
219    socket_addr.sin_port = htons(receiver_port); 
220  } 
221 
222  /* Establecemos el TTL de los datagramas. */ 
223  setsockopt(sd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)); 
224 
225  /* Reservamos memoria para el payload de los datagramas. */ 
226  char *payload = (char *)malloc(payload_size*sizeof(char)); 
227 
228  for(;;) /* Enviamos, enviamos y enviamos. */ { 
229 
230    /* Leemos la stdin. */ 
231    int bytes_read = read(0, (void *)payload, payload_size); 
232    fprintf(stderr,"r"); fflush(stderr); 
233    if(bytes_read < 1) break; 
234 
235    /* Escribimos la stdout, para poder continuar el pipe. */ 
236    write(1, (void *)payload, bytes_read); 
237    fprintf(stderr,"w"); fflush(stderr); 
238 
239    /* Enviamos el datagrama. */ 
240    sendto(sd, (void *)payload, bytes_read, 0, 
241      (struct sockaddr *)&socket_addr, sizeof(struct sockaddr)); 
242    fprintf(stderr,"s"); fflush(stderr); 
243 
244  } 
245}

2 El receptor

A continuación se muestra el correspondiente receptor de datagramas UDP. Este acepta los siguientes parámetros:

  1. El tamaño del payload del datagrama. Debe coincidir con el tamaño que se especifica en el emisor. Si es menor, el payload de los datagrmas entrantes será escrito en un bloque de memoria no reservado para tal propósito y el programa abortará (típicamente indicando que se ha producido un fallo de segmentación de memoria). Y si es mayor, desperdiciaremos algunos bytes y además en la salida estándar aparecerá periódicamente cierta “basurilla”, aquella que se encuentra en la zona de memoria que no ha sido escrita por el payload.
  2. El puerto de escucha. Sólo aquellos datagramas UDP que llegen a ese puerto serán recibidos por nuestro receptor. Evidéntemente, debería coincidir con el puerto de destino usado por el emisor.

Este es el programa udp_receive.c que puede descargarse desde http://www.ace.ual.es/\~vruiz/redes_industriales/udp_receive.c:

 
1/****************************************************************************** 
2 * udp_receive.c -- Receptor de datos que usa el UDP. 
3 * 
4 * Recibe un flujo de datos a traves de un puerto y lo copia a la 
5 * salida estandard. 
6 * 
7 * gse. 2010. 
8 *****************************************************************************/ 
9 
10/****************************************************************************** 
11 * 
12 * Ficheros cabecera. 
13 * 
14 *****************************************************************************/ 
15 
16/* Entrada y salida de streams buffereados. */ 
17#include <stdio.h> 
18 
19/* Biblioteca de funciones estandar (exit(), EXIT_SUCCESS, 
20   EXIT_FAILURE, ...) */ 
21#include <stdlib.h> 
22 
23/* Manipulacion de cadenas y movimiento de memoria (memset(), ...). */ 
24#include <string.h> 
25 
26/* Biblioteca estandar de funciones relacionadas con el sistema 
27   operativo Unix (read(), write(), getopt(), ...). */ 
28#include <unistd.h> 
29 
30/* Llamadas al sistema para manejo de descriptores de ficheros, 
31   sockets, etc. */ 
32#include <fcntl.h> 
33 
34/* Sockets. */ 
35/* Mas info en: 
36 * 
37 * http://www.csce.uark.edu/~aapon/courses/os/examples/concurrentserver.c 
38 * http://www.fortunecity.com/skyscraper/arpanet/6/cc.htm 
39 * http://beej.us/guide/bgnet/ 
40 */ 
41 
42/* Tipos de datos primitivos del sistema. */ 
43#include <sys/types.h> 
44 
45/* Sockets. */ 
46#include <sys/socket.h> 
47 
48/* Direcciones IP, opciones y definiciones. */ 
49#include <netinet/in.h> 
50 
51/* Servicio de resolucion de nombres. */ 
52#include <netdb.h> 
53 
54/* Signals (interrupciones). */ 
55#include <signal.h> 
56 
57/****************************************************************************** 
58 * 
59 * Definiciones. 
60 * 
61 *****************************************************************************/ 
62 
63/* Tamn~o del payload del datagrama. */ 
64#define PAYLOAD_SIZE 512 
65 
66/* Puerto de escucha por defecto. */ 
67#define LISTENING_PORT 6666 
68 
69/****************************************************************************** 
70 * 
71 * Variable globales. 
72 * 
73 *****************************************************************************/ 
74 
75/****************************************************************************** 
76 * 
77 * Funciones. 
78 * 
79 *****************************************************************************/ 
80 
81/* Cuerpo principal del programa. */ 
82int main(int argc, char *argv[]) { 
83  work(argc, argv); 
84  return EXIT_SUCCESS; 
85} 
86 
87work(int argc, char *argv[]) { 
88 
89  /* Taman~o de payload del datagrama. */ 
90  int payload_size = PAYLOAD_SIZE; 
91 
92  /* Puerto en el que el receptor escucha. */ 
93  int listening_port = LISTENING_PORT; 
94 
95  /* Manipulamos los parametros de entrada. */ 
96  int c; 
97  while ((c = getopt (argc, argv, "s:p:H")) != -1) { 
98    switch (c) { 
99    case s: 
100      payload_size = atoi(optarg); 
101      break; 
102    case p: 
103      listening_port = atoi(optarg); 
104      break; 
105    case H: 
106      fprintf(stderr, "Usage:receive[OPTION...]\n"); 
107      fprintf(stderr, "\n"); 
108      fprintf(stderr, "-spayload_size\n"); 
109      fprintf(stderr, "-plistening_port\n"); 
110      fprintf(stderr, "\n"); 
111      fprintf(stderr, "Receivesdatafromthelistening_portandcopiesthe"); 
112      fprintf(stderr, "datatothestdout\n"); 
113      return; 
114    case ?: 
115      if (isprint(optopt)) 
116        fprintf (stderr, "Unknownoption‘-%c’.\n", optopt); 
117      else 
118        fprintf(stderr, 
119                 "Unknownoptioncharacter‘\\x%x’.\n", 
120                 optopt); 
121      return 1; 
122    default: 
123      abort (); 
124    } 
125  } 
126 
127  fprintf(stderr,"%s:payloadsize=\"%d\"\n", argv[0],payload_size); 
128  fprintf(stderr,"%s:listeningport=\"%d\"\n", argv[0],listening_port); 
129 
130  int sd; /* Socket descriptor */ { 
131 
132    /* Obtenemos el numero del protocolo. */ 
133    struct protoent *ptrp; /* Pointer to a protocol table entry. */ 
134    ptrp = getprotobyname("udp"); 
135    if(ptrp == NULL) { 
136      perror("errroringetprotobyname()...aborting"); 
137      exit(EXIT_FAILURE); 
138    } 
139 
140    /* Creamos el socket descriptor. */ 
141    sd = socket(AF_INET, SOCK_DGRAM, ptrp->p_proto); 
142    if(sd < 0) { 
143      perror("socket"); 
144      exit(EXIT_FAILURE); 
145    } 
146  } 
147 
148  /* Si pulsamos CRTL+C, el programa acaba ejecutando la funcion 
149     end(). */ 
150  void end() { 
151    fprintf(stderr,"%s:CTRL+Cdetected!Exiting...", argv[0]); 
152    close(sd); 
153    fprintf(stderr,"done.\n"); 
154    exit(EXIT_SUCCESS); 
155  } 
156  /* Activamos la sen~al SIGINT. */ 
157  signal(SIGINT, end); 
158 
159  /* Cuando hagamos el bind(), usaremos el puerto de servicio sin 
160     esperar a que este a la espera de un time-out de 
161     liberacion. */ 
162  int yes = 1; 
163  if(setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) < 0) { 
164    perror("setsockopt"); 
165    exit(EXIT_FAILURE); 
166  } 
167 
168  /* Realizamos el bind() (peticion al SO para que a este proceso se 
169     le entregue todo lo que llega a traves de un determinado 
170     puerto). A la llamada bind() hay que pasarle tres parametros 
171     importantes: (1) el tipo de socket que vamos a utilizar, (2) la 
172     direccion IP del adaptador de red por el que vamos a recibir los 
173     datos y (3) el puerto de escucha. */ { 
174 
175    /* Estructura de datos que necesita bind(). */ 
176    struct sockaddr_in socket_addr; 
177    memset((char  *)&socket_addr,0,sizeof(socket_addr)); 
178 
179    /* (1) Usaremos Internet. */ 
180    socket_addr.sin_family = AF_INET; 
181 
182    /* (2) Cualquiera de los adaptadores de red (debidamente 
183       configurados con el IP) pueden recibir los datos. */ 
184    socket_addr.sin_addr.s_addr = htonl(INADDR_ANY); 
185 
186     /* (3) Definimos el puerto de escucha. */ 
187    socket_addr.sin_port = htons((u_short)listening_port); 
188 
189    /* Y finalmente, el bind(). */ 
190    if (bind(sd, (struct sockaddr *)&socket_addr, sizeof(socket_addr)) < 0) { 
191      perror("bind"); 
192      exit(EXIT_FAILURE); 
193    } 
194  } 
195 
196  /* Asignamos memoria para almacenar el payload del datagrama entrante. */ 
197  char *payload = (char *)malloc(payload_size*sizeof(char)); 
198 
199  for(;;) /* Bucle infinito de recepcion de datagramas. */ { 
200 
201    /* Recibimos un datagrama. */ 
202    int bytes_read = recv(sd, (void *)payload, payload_size, 0); 
203    fprintf(stderr,"r"); fflush(stderr); 
204 
205    /* Escribimos el datagrama a stdout. */ 
206    write(1, (void *)payload, bytes_read); 
207    fprintf(stderr,"w"); fflush(stderr); 
208 
209  } 
210}

Taller 1: Con los programas que hemos implementado es sencillo transferir un archivo entre dos hosts:

usuario@receiver:~$ ls udp_receive.c  
udp_receive.c  
usuario@receiver:~$ make udp_receive  
cc     udp_receive.c   -o udp_receive  
usuario@receiver:~$ ./udp_receive -H  
Usage: udp_receive [OPTION...]  
 
  -s payload_size  
  -p listening_port  
 
Receives data from the listening_port and copies the data to the stdout  
usuario@receiver:~$ ./udp_receive > file  
./udp_receive: payload size = "512"  
./udp_receive: listening port = "6666"  
 
# Ahora el receptor est\’a esperando a que le lleguen datos ...  
 
usuario@sender:~$ ls udp_send.c  
udp_send.c  
usuario@sender:~$ make udp_send  
cc     udp_send.c   -o udp_send  
usuario@sender:~$ ./udp_send -H  
Usage: udp_send [OPTION...]  
 
  -s payload_size  
  -h receiver_host  
  -p receiver_port  
  -t TTL  
 
Copies the stdin to the stdout and a UDP socket  
usuario@sender:~$ ./udp_send < udp_send.c > /dev/null  
 
# Cuando la transmisi\’on se haya producido (esto ha ocurrido  
# cuando dejan de aparecer caracteres "rw" en el receptor),  
# se puede detener el receptor pulsando CTRL+C.

Ejercicio 1: ¿Se le ocurre alguna forma de evitar tener que pulsar CTRL+C en el receptor cuando la transmisión haya finalizado?