Practicas
Practicas
Práctica 6: Make
Índice
Objetivos
Herramienta make:
Estructura típica de un proyecto
Organización de proyectos utilizando el make.
Caso 1) Integración de dos archivos tipo texto:
Caso 2) Integración de archivos textos y archivos objeto:
Caso 3) Integración de archivos textos y bibliotecas:
Laboratorio:
Lecturas sugeridas:
Anexo 1 Bibliotecas compartidas
Creación
Compilar programas
Ejecutar programas usando biblitoecas dinámicas
Referencias
Objetivos
Esta práctica se enfoca en la administración de proyectos de programación debido a
que estos sistemas operativos siempre han sido ideales para el desarrollo de
aplicaciones; incluyendo tanto rutinas para servidores como para crear todo un nuevo
servicio o aplicación de usuario desde cero.
En esta práctica aprenderás a utilizar dicha herramienta de forma básica para compilar
múltiples rutinas, tal vez desarrolladas por distintos programadores y que dependen
unas de otras; además, aprenderás, tu resultado final podrá incluir librerías estáticas
de rutinas previamente compiladas.
Herramienta make:
Cuando uno está desarrollando una aplicación el objetivo final es crear un solo archivo
ejecutable. Sin embargo, es conveniente dividir el trabajo en pequeños módulos. Estos
módulos finalmente se tendrían que integrar para formar el ejecutable, y pueden ser
archivos en un lenguaje particular (i.e. Lenguaje C), o pueden ser códigos binarios
(e.g. código objeto o bibliotecas de código objeto tales como stdio, que incluye varios
códigos objeto para realizar input/output desde C).En sistemas basados en Unix es
muy común utilizar Make para organizar proyectos compuestos de múltiples módulos.
Primera línea: “Para lograr el objetivo arch.o (es decir, para poder obtener arch.o), es
necesario que antes exista su versión fuente, arch.c. O sea, arch.o depende de la
existencia previa de arch.c.”
Segunda línea: “Una vez que exista arch.c, se ejecuta el comando cc arch.c (es decir,
se compila arch.c. Eso nos produce arch.o”. Además, el comando nos dice: “si llega a
cambiar arch.c, para obtener arch.o recompile ese arch.c. Si no ha cambiado arch.c,
no es necesario recompilarlo”. Todo eso dicen esas dos líneas.
objetivo2: dependencias2
<tab>comandos2
(...)
Los campos denominados “objetivos” son aquellos campos que Makefile puede
ejecutar por separado si se introducen como argumento del comando make. Ejemplo:
“make objetivo1” ejecutará exclusivamente el objetivo “objetivo1”. En el ejemplo
anterior, podríamos entonces decir “make arch.o”. Ahora, las dependencias son otros
archivos u objetivos que deben ya existir sin cambio o que se deben de ejecutar antes
de poder realizar el objetivo en cuestión. Los comandos son por lo regular comandos
para compilar uno o más códigos fuentes, incorporar códigos objetos, o crear o
incorporar bibliotecas dinámicas o estáticas entre otros.
El archivo Makefile se utiliza para estructurar las dependencias entre módulos de tal
modo que sólo se vuelvan a compilar aquellos módulos que hayan sido modificados.
Podemos ver el comando como una especie de “shell scripting” enfocado
totalmente a compilación e integración de proyectos (en este laboratorio
utilizaremos solamente C/C++ pero Makefiles también puede funcionar para otros
lenguajes como Java)..
Como quiera, estos módulos pueden ser otros archivos de código fuente, código
binario ( archivo objeto o biblioteca) y se pueden apreciar como los siguientes:
.o ?
Posiblemente como alumno esté acostumbrado al código C/C++, pero por el objetivo
de las clases donde aprendió a programar su código basaba de estar en Texto a ser
código ejecutable. De esa etapa a la ultima existen varias sub-etapas que se utilizan y
están identificadas en el documento “Complemento: Programación C”
En general, las fases por las que pasa todo código en C hacia el archivo ejecutable
son:
miProj
| - bin
| - doc
| - sources
| | - proj.c
| |- makefile
|- miIncludes
|- aritReal.h
a= suma(3.5,3.4);
b= resta(4.0,2.0);
c= multiplica(3.0,5.0);
d= divide(7.0,3.0);
Es muy común que el archivo Makefile se utilice para ver dependencias entre archivos
y así solo se compilen aquellas partes del código que fueron afectadas. En este
ejemplo, el archivo Makefile(1) se muestra a continuación:
CC= gcc #/*string que se substituye mas adelante dondequiera que aparezca*/
ARGS=-O #/*otro string. Indica: "optimiza el código" */
proj.o: proj.c
<tab>$(CC) $(ARGS) -c proj.c #borrar <tab> y oprimir la tecla Tab
clean:
<tab>rm *.o #borrar <tab> y oprimir la tecla Tab
En español, este archivo Makefile nos dice que: “para obtener proj (primer
objetivo), es necesario que existan proj.o y aritReal.h. Una vez que estos dos
existen, se obtiene proj utilizando el compilador gcc, produciéndose como
objeto proj (-o ..bin/proj) a partir de proj.o. Ahora bien, para que pueda existir proj.o
(segundo objetivo), se requiere que exista proj.c; una vez que existe proj.c,
simplemente se compila. Si cambiara proj.c, al ejecutar el comando "make” se
recompilaría. Si cambiara arithReal.h, pues también se recompilaría proj.c
automáticamente, ya que el primer objetivo nos indica que arithReal.h es requisito para
poder obtener proj y que éste depende de proj.o y que este último depende de proj.c
que ya cambió pues incluye a arithReal.h”. . . Bueno no se si lo prefieres así en
español o mejor de manera más formal:
CC y ARGS son constantes textuales que se sustituirán por sus valores donde
quiera que aparezcan en el cuerpo del texto
proj es un objetivo que tiene dependencia del código objeto, proj.o, y del
archivo ../miIncludes/aritReal.h. Es decir, si se ejecuta desde la línea de
comandos "make proj" y se ha modificado alguno de estos dos archivos,
entonces se ejecuta el comando que sigue, el cual obtiene el objetivo proj.
El compilador nos dice que el código ejecutable se almacenará en el
directorio ../bin/.
Nos dice que el archivo objeto proj.o tiene dependencia del código fuente
proj.c. Es decir, que si se modifica el código fuente se obtendrá el código objeto
de este fuente.
Además, nos dice que el objetivo clean no tiene dependencias y por lo tanto,
cada vez que ejecutemos esta opción ("make clean")se realizará el siguiente
comando rm *.o, que borrará del directorio todos los archivos objeto
Ahora, para obtener el código del proyecto se utiliza el comando make , como sigue:
user@localhost> cd miProj/sources
user@localhost> make
cc -O -c proj.c
cc -o ../bin/proj proj.o
user@localhost>
Ejemplo:
Se utilizará el directorio objetos para almacenar el archivo fuente y el objeto de la
biblioteca, y el directorio sources para almacenar el fuente y objeto del programa
proj.c. Entonces, la organización de los directorios del proyecto es como sigue:
miProj
| - bin
| - doc
| - objetos
| | - aritReal.c
| |- Makefile
| - sources
| | - proj.c
| |- Makefile
| - miIncludes
| | - aritReal.h
clean:
<tab>rm *.o
o en forma de árbol:
miProj
| - bin
| - doc
| - objetos
| | - aritReal.c
| | - aritReal.o
| |- Makefile
| - sources
| | - proj.c
| |- Makefile
Ahora que tenemos el código objeto, nos vamos al directorio miProj/sources. En este
directorio se encuentran el código fuente proj.c y el archivo Makefile de nuestro
proyecto.
proj.o: proj.c
<tab>$(CC) $(ARGS) -c proj.c
clean:
<tab> rm *.o
a= suma(3.5,3.4);
b= resta(4.0,2.0);
c= multiplica(3.0,5.0);
d= divide(7.0,3.0);
fprintf(stdout,"a= %f\n b= %f \n c= %f \n d= %f\n", a, b, c, d);
}
Aunque el encabezado aritReal.h está presente al igual que en el caso anterior, ahora
solo posee los encabezados de las funciones como se muestra a continuación
/* archivo 'aritReal.h' es almacenado en el directorio 'miIncludes' */
user@localhost> proj
a= 1074528256.000000
b= 1074790400.000000
c= 1074266112.000000
d= 1075576832.000000
En el caso de las librerías estáticas, las librerías son anexadas al programa que las
utiliza al final del proceso de compilación. Esta fase final se denomina “ligado” o
“linking” en inglés. En este escenario, más de un programa puede utilizar la misma
librería pero estarán duplicadas en cada programa.
Ejemplo:
miProj
| - bin
| - doc
| - biblioteca
| | - aritReal.c
| |- Makefile
| - sources
| | - proj.c
| |- Makefile
| - miIncludes
| | - aritReal.h
aritReal.o: *.c
<tab>gcc -c -Wall *.c
libreria: $(modules)
<tab>ar -cvq libMia.a *.o
clean:
<tab>rm *.o
<tab>rm libMia*
Observe que en esta ocasión se están utilizando dos programas distintos: gcc y ar; el
primero es el compilador mientras el segundo se utiliza para crear una biblioteca que
contenga el resultado de las compilaciones. Se ha añadido el objetivo "all" para
garantizar que el uso del comando make genere los códigos objetos y la creación de
la librería (en realidad, inicia la creación de librería pero para lograrlo debe cumplir
primero con el objetivo aritReal.o).
user@localhost> ls
aritReal.c Makefile
user@localhost> make
gcc -c -Wall *.c
ar -cvq libMia.a *.o
user@localhost> ls
user@localhost>
int main()
{
float a,b,c,d;
a= suma(3.5,3.4);
b= resta(4.0,2.0);
c= multiplica(3.0,5.0);
d= divide(7.0,3.0);
fprintf(stdout,"a= %f\n b= %f \n c= %f\n d=%f\n", a, b, c, d);
}
proj: proj.o
$(CC) -o ../bin/proj proj.o -L$(DIR) -lMia
proj.o: proj.c
clean:
rm *.o
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../miIncludes/aritReal.h"
int main()
{
float a,b,c,d;
a= suma(3.5,3.4);
b= resta(4.0,2.0);
c= multiplica(3.0,5.0);
d= divide(7.0,3.0);
= = = = Laboratorio = = = =
Actividades a realizar en esta práctica se encuentran descritas en este
documento, sus respuestas deben registrarse en :
Ejercicio 1
Los resultados se deberán imprimir en pantalla y guardar en el archivo salidaUno.txt. Una
vez que tengas el los archivos de código: numerosUno.c, cuadrado.c, cubo.c, cuarta.c (nota
que son cuatro archivos separados, no uno solo con todas estas funciones), crea un archivo
único Makefile que te permita compilar todos los archivos.
Primero define tres reglas que obtengan el código objeto de los archivos cuadrado.c, cubo.c y
cuarta.c; y luego define otra regla para crear el programa numerosUno a partir de los archivos
numerosUno.c, cuadrado.o, cubo.o y cuarta.o. También define una regla clean para borrar los
archivos objeto después de la compilación.
Ejercicio 2
Desarrolla la biblioteca potencias, que contiene las funciones especificadas en la Tabla 1 (un
único archivo). A partir de el programa anterior, desarrolla el programa numerosDos.c, que
realiza las mismas operaciones aritméticas, pero que utiliza la biblioteca potencias en lugar de
los archivo objeto como fuente de código para las funciones aritméticas.
Lecturas sugeridas:
La siguiente liga posee ejemplos de Make: http://mrbook.org/tutorials/make/
Compilar programas
Para compilar programas que hagan uso de una biblioteca compartida es necesario utilizar la
bandera -L para especificar el lugar donde se encuentra dicha biblioteca.
LD_LIBRARY_PATH=/path/a/mi/biblioteca/;$LD_LIBRARY_PATH mi_programa
Referencias
http://www.dwheeler.com/program-library/Program-Library-HOWTO/x36.html
Laboratorio de
Sistemas Operativos
Objetivo
En esta práctica nos enfocaremos en los elementos básicos para el desarrollo de
aplicaciones para ambiente GNU/Linux utilizando el lenguaje nativo del mismo: C.
Nos enfocaremos principalmente en diferenciar el uso de código de usuario y código
de sistema.
Índice
Desarrollo de programas usando Programación de sistemas
---- Algunas llamadas a sistema---
Lectura mediante read( )
Escribiendo con write( )
Cerrar archivos
--- Un ejemplo de llamadas al sistema ---
File I/O: writeback , Synchronized I/O
Synchronized I/O
Lecturas predictivas
I/O usando buffers (lenguaje C estándar)
Apuntador a archivo
Leyendo Streams
Escribiendo a un stream
La biblioteca estándar de C implementa varias funciones para escribir en un
stream abierto.
Ejemplo de un programa que usa I/O en buffers.
Mapeo a Memoria (Memory map)
Referencias
Fuentes adicionales
Software del sistema: es aquel que está en contacto directo con el manejo del
kernel y las bibliotecas del sistema. El desarrollo de software de sistema exige
que el usuario conozca sobre el hardware del sistema en el que va a trabajar el
software.
La mayoría del código para Linux y Unix se escribe “a nivel sistema”. Ya que las
llamadas al sistema son dependientes del hardware, mucho de este código se
escribe en lenguaje C.
Las llamadas al sistema son funciones que se invocan desde nivel usuario hacia el
kernel para requerir un servicio o recurso del sistema operativo.
Cada Sistema Operativo define diversas llamadas al sistema. Por ejemplo en el caso
de sistemas operativos Windows, se definen miles de llamadas al sistema. En el caso
de Linux el número de llamadas al sistema son aproximadamente unas 300.
Abrir archivos:
Antes que los datos de un archivo se puedan leer, éste debe de ser abierto utilizando
las llamadas a sistema open() o create().
La definición de estas funciones, así como las constantes utilizadas en los “flags”, se
pueden encontrar en los siguientes encabezados.
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
Cada llamada lee hasta una cantidad de len bytes a partir de la posición actual
referenciada por el descriptor de archivo fd, y los guarda en buf. Regresa el número de
bytes escritos en buf, o -1 si ocurre un error.
Cerrar archivos
#include <unistd.h>
int close (int fd);
/* PROGRAMA: copiar.c
FORMA DE USO:
./copiar origen destino
VALOR DE RETORNO:
0: si se ejecuta satisfactoriamente.
-1: si se da alguna condicion de error
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h> /* open() */
#include <sys/stat.h> /* open() */
#include <fcntl.h> /* open() */
#include <unistd.h> /* read() y write() */
close(fd_origen);
close(fd_destino);
}
A la hora de ejecutar, observe que cuando no hay error, el programa envía al sistema
operativo el valor 0, y si hay error el programa envía al sistema operativo el valor de -1.
El sistema operativo toma el valor y lo almacena en la variable $?. Entonces, el SO
puede utilizar esta información en un programa shell para tomar decisiones. A
continuación se muestra el ejemplo de un caso con error:
Aún cuando una aplicación pueda creer que se realizó la escritura con éxito, en
caso de falla los datos podrían no haber llegado al disco.
Un problema final con las escrituras retrasadas tiene que ver con el reporte de
ciertos errores de I/O. Cualquier error de I/O que ocurra durante la escritura a
disco —por ejemplo, una falla física de disco— no puede ser reportada al
proceso que emitió la petición de escritura.
El kernel trata de minimizar los riesgos de estas escrituras diferidas. Para asegurar
que los datos son escritos oportunamente, el kernel establece una edad máxima del
buffer, y escribe a disco todos los buffers “sucios” antes de que maduren más allá del
tiempo dado. Los usuarios pueden configurar este valor en el archivo
/proc/sys/vm/dirty_expire_centisecs. El valor está especificado en centésimas de
segundo.
Synchronized I/O
Aunque la sincronización de I/O es un tema importante, los problemas asociados con
las escrituras retrasadas no deben ser temidos. Las escrituras mediante buffers
proporcionan una mejora enorme en el rendimiento, y consecuentemente, cualquier
sistema operativo que merezca la etiqueta de “moderno”, implementa escrituras
diferidas mediante buffers. Sin embargo, hay ocasiones en las que las aplicaciones
desean controlar de manera precisa el momento en que los datos llegan al disco. Para
esos usos, el kernel de Linux proporciona un puñado de opciones que permiten
negociar el rendimiento por medio de las operaciones sincronizadas.
fsync() asegura que todos los datos sucios asociados con el archivo mapeado por el
file descriptor fd sean escritos al disco en el momento en que se ejecuta esa llamada.
Menos óptimo, pero de más amplio alcance, la llamada a sistema sync() se
proporciona para sincronizar todos los buffers al disco.
if ((fd_destino=open(argv[2],O_WRONLY|O_TRUNC|O_CREAT,0666))== -1)
podríamos incluir la bandera O_SYNC de la siguiente forma:
if ((fd_destino=open(argv[2],O_WRONLY|O_TRUNC|O_CREAT|O_SYNC, 0666))==
-1)
Lecturas predictivas
"Readahead" se refiere a la acción de leer más datos del disco y de la página de
caché en la que se hizo la petición de lectura, en efecto, leyendo un poco hacia
adelante.
El problema, por supuesto, es que los programas rara vez efectúan estas operaciones
en términos de bloques. Los programas trabajan con campos, líneas o caracteres, no
con abstracciones como los bloques. Para remediar esta situación, los programas
pueden utilizar las funciones de usuario para I/O con buffers de usuario, declarados
por el programador. La biblioteca estándar de C provee la librería estándar de I/O
(comúnmente llamada stdio), la cual a su vez provee una solución de utilización de
buffers para usuarios, de forma independiente a la arquitectura sobre la que corra el
sistema. Estas rutinas de lectura y escritura utilizando buffers de usuarios pueden
utilizarse incluso no solo para escribir a disco, sino para escribir a cualquier dispositivo
futuro o incluso para escribir a “sockets” de red y así enviar “writes” y “reads”
provenientes de otros programas, posiblemente residentes en otras máquinas de la
red.
Que una aplicación use las funciones estándar de I/O o los accesos directos a las
llamadas de sistema es una decisión que el desarrollador debe tomar muy
cuidadosamente después de sopesar las necesidades de la aplicación y su
comportamiento.
Apuntador a archivo
Es un apuntador al tipo de dato FILE, definido en <stdio.h>, que representa el
apuntador a archivo. Hablando del estándar I/O , un archivo abierto es llamado
"stream". Estos Streams pueden abrirse para escritura, lectura o ambos.
Para abrir un archivo se utiliza el la función fopen() y para cerrarlo fclose().
Leyendo Streams
La biblioteca estándar de C implementa diferentes funciones para leer de un stream
abierto.
Para mayor referencia sobre estas funciones, consultar los manuales de cada función.
Escribiendo a un stream
La biblioteca estándar de C implementa varias funciones para escribir en un stream
abierto.
/* PROGRAMA: fcopiar.c
FORMA DE USO:
fcopiar origen destino
VALOR DE RETORNO:
0: si se ejecuta satisfactoriamente.
-1: si se da alguna condicion de error
*/
#include <stdio.h>
#include <stdlib.h>
fclose(forigen);
fclose(fdestino);
return 0;
}
Por estas razones, mmap() es una opción inteligente para muchas aplicaciones.
Los mapeos a memoria debe ajustarse dentro del espacio de direcciones del
proceso. Con un espacio de direcciones de 32 bits, un número muy grande de
mapeos de diferentes tamaños podría resultar en la fragmentación del espacio
de direcciones, haciendo difícil encontrar grandes regiones libres contiguas. Este
problema, por supuesto, es mucho menos aparente con espacios de direcciones
de 64 bits.
Por estas razones, los beneficios de mmap() son extremadamente notorios cuando el
archivo mapeado es grande (y así cualquier espacio desperdiciado es un pequeño
porcentaje del mapeo total), o cuando el tamaño total del archivo mapeado es divisible
por el tamaño de página (y así no hay desperdicio de espacio).
nbytes = statbuf.st_size;
close(fd_origen);
munmap(src, statbuf.st_size);
close(fd_destino);
return 0;
}
Note que este programa utiliza las mismas funciones fread y fwrite para leer y escribir
archivos mapeados a memoria.
Una implementación alternativa podría evitar la doble copia al hacer que cada petición
de lectura regrese un apuntador al buffer I/O estándar. Los datos podrían entonces ser
leídos directamente, dentro del buffer I/O estándar, sin la necesidad de una copia
extraña. En el evento de que la aplicación quisiera que los datos se almacenarán en
su propio buffer local—quizás escribir a él—podría siempre realizar la copia
manualmente. Esta implementación proveería una interfaz “libre”, permitiendo a las
aplicaciones indicar cuando se apropian de un pedazo del buffer de lectura. Las
escrituras serían un poco más complicadas, pero aún se evitaría la doble copia.
Cuando se emite una petición de escritura, la implementación almacenaría el puntero.
Al final, cuando se está listo para vaciar los datos al kernel, la implementación podría
recorrer su lista de apuntadores almacenados, escribiendo a disco los datos. Esto
podría ser hecho usando I/O de dispersión-reunión, vía writev(), y por lo tanto solo
una llamada a sistema. Existen bibliotecas user-buffering altamente óptimas, que
resuelven el problema de la doble copia con implementaciones similares a las que se
han discutido. Alternativamente, algunos desarrolladores eligen implementar sus
propias soluciones. Pero a pesar de estas alternativas, I/O estándar permanece
popular.
= = = = Laboratorio = = = =
Actividades a realizar en esta práctica se encuentran descritas en este
documento, sus respuestas deben registrarse en :
Sin embargo, por tragedias que involucran un CPU abierto durante el mantenimiento y
malas prácticas de dónde colocar un refresco de cola, el disco duro que contenía el
código se daño y solo se consiguió recuperar fragmentos del código fuente orginal. Es
su deber reescribir el código partiendo de lo que se recuperó y que se despliega en la
tabla de mas abajo. Como buen desarrollador, entregará un archivo de parche
diferencial con los cambios en las lineas, es decir, utilizará la herramienta diff. Para
indicar cuales son las líneas restauradas y su nuevo valor.
#include <stdio.h>
#include <stdlib.h>
#include <sys/____.h> /* open() */
#include <sys/____.h> /* open() */
#include <____.h> /* open() */
#include <____.h> /* read() y write() , close() */
for(____;i<3;i++) {
close(____);
}
user@localhost ~$ ls
body.c fusionar.c fusionar headers.c
user@localhost ~$ cat headers.c
#include <stdio.h>
user@localhost ~$ cat body.c
const char *msg = "Hello Earthlings!";
int main ()
{
printf ("Message from Mars: %s\n", msg);
return 0;
}
user@localhost ~$ ./fusionar headers.c body.c secretProgram.c
user@localhost ~$ ls
body.c fusionar.c fusionar headers.c sec
retProgram.c
user@localhost ~$ gcc -Wall -o secretProgram secretProgram.c
user@localhost ~$ ./secretProgram
Message from Mars: Hello Earthlings!
Referencias
[1] Love, Robert. Linux System Programming, O'REILLY, 2007. Biblioteca: QA76.76.O63
L69 2007
[2] Marquez, Fernando. Programación Avanzada en UNIX, 3a. edicion, 2004. Biblioteca
QA, 76.76, .O63, M37, 2006 (Reserva)
Fuentes adicionales
Objetivo
En está práctica seguimos con los temas de desarrollo de aplicaciones en ambiente
GNU/Linux pero en esta ocasión nos enfocamos más en la definición de “Archivo”.
Como recordarán hemos hecho mucho énfasis en que en Linux “todo es un archivo”
y esto requiere especial atención cuando uno está por adentrarse con el hardware o
con otros elementos. Por lo anterior, en esta ocasión nos enfocaremos en cómo se
maneja el sistema de archivos en un S.O. GNU/Linux.
ÍNDICE
Objetivo
Tipos de Archivos
Archivos Regulares
Directorios
Enlaces (hard and soft links)
Archivos especiales
tree.c, Desplegando el tipo de archivo.
Arquitectura del Sistema Operativo
Arquitectura del sistema de archivos
Descriptores de archivos e I-Node
File descriptors.
i-Node
Estado1.c
Tablas de control de acceso a los archivos.
Copiar.c
Laboratorio
Ejercicio 1
Ejercicio 2
Ejercicio 3
Conclusiones
Referencias:
Tipos de Archivos
"Archivo" es el concepto más básico y fundamental de Linux, y no necesariamente se
refiere a lo que normalmente conocemos como “archivos en disco”. Linux trata a los
dispositivos y periféricos (terminales, teclados, unidad USB, CD roms etc.) como si
fueran archivos (everything is a file philosophy).
Archivos Regulares
Lo que la mayoría de nosotros llamamos “archivo” es lo que linux etiqueta como
archivos regulares. Un archivo regular contiene bytes de datos, organizados en un
arreglo lineal llamado flujo de bytes (byte stream). Ejemplos de estos son los archivos
de texto, documentos generados por alguna suite de oficina, archivos ejecutables, etc.
Directorios
Actúan como un contenedor para otros archivos y directorios; en otros ambientes
suelen llamarse “folders”. Un directorio es en sí un archivo, que contiene una lista de
nombres de otros archivos o directorios contenidos dentro del directorio,. Cada uno de
estos nombres está a su vez asociado con un número de nodo-i (i-node), que es un
identificador del archivo interno al kernel.
Los directorios son los archivos que nos permiten darle una estructura
jerárquica a los sistemas de archivos de Linux; por ejemplo el sistema o
conjunto de archivos que reside en una partición de un disco duro o un USB.
Archivos especiales
Archivos de dispositivos basados en caracteres, como el “archivo” teclado,
Archivos de dispositivos basados en bloques, como los archivos en disco.
En los dispositivos de modo bloque hay un buffer que mejora enormemente la
velocidad de transferencia. Un mismo dispositivo puede ser accedido de modo
bloque o de modo carácter dependiendo del “driver” utilizado.
Pipes con nombre, que como sabemos se utilizan para ligar la salida (standard
out) de un programa con la entrada (standard in) de otro y
Sockets, que se utilizan para inter-comunicar programas que pueden residir en
la misma o diferentes máquinas.
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <string.h>
struct opciones
{
unsigned mostrar_archivos:1;
};
for (i=1;i<argc;i++)
if (argv[i][0] =='-')
for (j=1;argv[i][j]!=0;j++)
switch (argv[i][j])
{
case 'f':
opciones.mostrar_archivos= SI;
break;
default:
fprintf(stderr, "Opcion [-%c] desconocida \n",argv[i][j]);
}
return 0;
}
/*Apertura de directorio.*/
if ((dirp=opendir(path)) == NULL)
{
perror(path);
return;
};
++nivel;
tree(archivo,opciones);
--nivel;
}
/* Si el archivo no es un directorio y esta activa la opcion
Mostrar_archivos (-f), presentamos por pantalla el
nombre del
archivo y su tipo.*/
else {
if (ok !=-1 && opciones.mostrar_archivos== SI) {
for (i=0;i<nivel;i++)
printf ("\t");
switch (buf.st_mode & S_IFMT)
{
case S_IFREG:
if (buf.st_mode & 0111)
tipo_archivo = 'x';
else
tipo_archivo = 'o';
break;
case S_IFCHR:
tipo_archivo = 'c';
break;
case S_IFBLK:
tipo_archivo = 'b';
break;
case S_IFIFO:
tipo_archivo = 'p';
break;
default:
tipo_archivo = '?';
} //switch
fprintf(stdout,"(%c) %s \n", tipo_archivo, dp->d_name);
}else
fprintf(stdout," %s \n", dp->d_name);
}
} /* while */
closedir(dirp);
} /* tree */
[user@localhost home]$ du
22 ./Finales/al149830
18 ./Finales/al246793
14 ./Finales/al158072
170 ./Finales/al171574
10 ./Finales/al171465
50 ./Finales/al501240
16 ./Finales/al501747
16 ./Finales/al177269
320 ./Finales
72 ./CyUnixAvanzado
10 ./pruebas
402 .
El valor mostrado indica cuántos bloques poseen los archivos en el directorio indicado
en la línea de comandos del du (default: el directorio local) incluyendo subdirectorios y
el mismo directorio (en el ejemplo, el directorio local ocupa 402 bloques incluyendo
todos sus subdirectorios y archivos).
Los bloques son importantes peus son la referencia directa a los clusters del disco
duro donde se almacenan (o los bloques en memorias flash), sin embargo, nosotros
estamos acostumbrados a ver el tamaño en términos de Bytes y es posible lograrlo
con el argumento -h.
Una distribución de linux o distro, tal como Ubuntu o Fedora, agrega al Kernel un
shell, un conjunto de comandos, una interfase tipo GUI y varias aplicaciones útiles;
pero todas las distribuciones utilizan básicamente el mismo Kernel, que ha sido
desarrollado y mantenido durante muchos años por una extensa comunidad
encabezada por Linus Torvald.
Durante las prácticas pasadas ya hemos estudiado el manejo de las librerías de GNU
ya que se trata de las herramientas proveídas en cualquier distro basado en Linux
(Ubuntu, Fedora, Red-hat , Arch, etc...) y se trata de comandos como cd, awk, ls, rm,
tar, rsync, etc... que pueden formar parte de shell scripts. Cuando usamos cualquier
comando tal como “rm -f /”, el Kernel valída primero los privilegios y permisos del
usuario que lo manda a ejecutar antes de realizar la acción. Con el comando mostrado
en particular, seguramente no lo realizará pues a excepción de Root nadie tiene
control del directorio raíz (Por cierto, ese comando tiende a ser una broma pesada en
los foros de Linux).
Además ya hemos manipulado la interacción con el hardware al crear programas que
copian archivos dentro del disco duro, ya sea directamente con los comandos read y
write o utilizando librerías de usuario (standard I/O).En ambos casos, es el Kernel
quien toma el control., sin embargo a momentos distintos, esto se puede apreciar con
la siguiente imagen donde se vuelven a marcar las 3 capas pero desplegando más
ampliamente las acciones que se realizan:
Un sistema de archivos:
Es una colección de archivos y directorios en una jerarquía válida y formal.
Los sistemas de archivos pueden ser agregados y removidos individualmente
del espacio de nombres global de archivos y directorios.
Linux también soporta sistemas de archivos virtuales que existen sólo en
memoria, y sistemas de archivos de red que existen en máquinas a través de la
red.
Los sistemas de archivos físicos residen en dispositivos de almacenamiento en
bloque, como CDs, unidades flash, tarjetas compact flash, o discos duros.
Algunos de estos dispositivos son divisibles en particiones, lo que significa que
pueden ser divididos en múltiples sistemas de archivos, los cuales pueden ser
manipulados individualmente.
File descriptors.
Antes de que un archivo pueda ser leído o escrito, debe ser abierto. El kernel mantiene
una lista de archivos abiertos por proceso, llamada la tabla de archivos. Esta tabla es
indexada a través de enteros no negativos conocidos como file descriptors
(comúnmente abreviado fd). Cada entrada en la lista contiene información sobre un
archivo abierto, incluyendo un apuntador a una copia en memoria del i-node del
archivo y meta-datos asociados, como son la posición del archivo y los modos de
acceso. Tanto el espacio de usuario como el espacio de kernel usan file descriptors
como cookies únicas por proceso. Abrir un archivo regresa un file descriptor, mientras
las operaciones subsecuentes (leer, escribir, y así sucesivamente) toman el file
descriptor como su argumento primario.
i-Node
Un archivo posee varios componentes:
nombre,
contenido e
información administrativa (permisos y fechas de modificación).
Cada archivo tiene asociado un “nodo-i”. El i-node tiene información necesaria para
que un proceso pueda acceder a los archivos. Los campos de un i-node son:
Tipo de archivo: ordinario, directorio, especial o tubería (pipe).
Tipo de acceso: permisos de usuario, grupo y resto de usuarios.
Tiempos de acceso al archivo: fecha de última modificación, fecha de la última
vez que se accedió al archivo, y de la última vez que se modificó.
Tamaño del archivo.
Apuntadores a bloques de disco donde se almacena el contenido del archivo:
los archivos no tienen porque estar físicamente en bloques continuos.
El SO carga la lista de nodos-i del disco a memoria principal, conocida como tabla de
nodos-i.
La tabla de nodos-i contiene la información de la lista de i-nodes más otros datos como
estado el estado de cada nodo-i (p. ej., si el nodo-i esta bloqueado, si hay algún
proceso que esté esperando que el nodo-i se desbloquee, si la copia del inode que
hay en memoria es diferente a la copia de disco, etc.)
Estado1.c
A continuación se presenta el programa estado1.c, que muestra el tipo de un archivo
basándose en la información contenida en su nodo-i:
/* PROGRAMA: estado1.c (basado en programa 3.10 en tercera edicion.)
DESCRIPCION:
Presenta por pantalla la informacion estadistica de
nombre_archivo.
FORMA DE USO:
estado <nombre_archivo> [<nombre_archivo> ...]
VALOR DE RETORNO:
0: si se ejecuta satisfactoriamente.
-1: si se da alguna condicion de error.
*/
#include <stdio.h>
#include <stdlib.h>
#include <pwd.h>
#include <grp.h>
#include <sys/types.h>
#include <sys/stat.h>
char permisos[]={'x','y','r'};
fprintf(stdout," Tipo:");
/* Analisis del tipo de dispositivo*/
/* Para concer el tipo de archivo, se utilizan unas constantes
definidas*/
/* en sys/stat.h*/
/* S_IFMT= 0170000 */
/* S_IFREG=0100000 */
/* S_IFDIR=0040000 */
/* S_IFCHR=0020000 */
/* S_IFBLK=0060000 */
/* S_IFIFO=0010000 */
switch (buf.st_mode & S_IFMT) /* mascara para obtener el tipo de
archivo*/
{
case S_IFREG:
printf("ordinario\n");
break;
case S_IFDIR:
printf("directorio\n");
break;
case S_IFCHR:
printf("especial modo de caracter\n");
break;
case S_IFBLK:
printf("especial modo bloque\n");
break;
case S_IFIFO:
printf("FIFO");
break;
}
}
/* Ciclo de conteo */
for (i=1; i<argc; i++)
{
estado(argv[i]);
}
exit(0);
return 0;
}
[username@localhost home]$ ls
copiar.c hola
[username@localhost home]$ ./estado1 copiar.c
Archivo: copiar.c
Nro. de inode: 20578307
Tipo:ordinario
[username@localhost home]$ ./estado1 hola
Archivo: hola
Nro. de inode: 20578313
Tipo:directorio
Otra forma de ver los nodos-i de los archivos es utilizando la opción -i del comando ls
como se muestra a continuación:
[username@localhost home]$ ls -i
1145832168 alphawords 1145831648 alphawords~
1145832166 fruits
Además de la tabla de i-nodes, el Kernel mantiene en memoria otras dos tablas con
información necesaria para manipular un archivo: tabla de archivos y la tabla de
descriptores de archivo.
La tabla de archivos es una estructura global del kernel y en ella hay una entrada
para cada archivo que los procesos del kernel o del usuario tienen abiertos.
Cuando un proceso invoca una llamada (open, creat, dup, link) para hacer una
operación sobre un archivo, le va a pasar al kernel el descriptor de archivos. El kernel
va a utilizar ese número para acceder a la tabla de archivos del proceso, y buscar en
ella cual es la entrada de la tabla de archivos que le da acceso a su nodo-i. A través
del nodo-i es que se tiene acceso a los datos. Si el usuario crea otros archivos,
entonces el número de descriptor de archivo continua a partir de este numero.
Copiar.c
/* PROGRAMA: copiar.c
FORMA DE USO:
./copiar origen destino
VALOR DE RETORNO:
0: si se ejecuta satisfactoriamente.
-1: si se da alguna condicion de error
*/
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
char buffer[BUFSIZ];
close(fd_origen);
close(fd_destino);
return 0;
}
al ejecutralo generaria:
Dos archivos pueden tener asignado el mismo nodo-i. El comando utilizado para asignar
ligas(hard links o soft links) a nombres de archivo es “ln”.
El comando rm en realidad no borra nodos-i; borra entradas del directorio a nodos-i. Solo
cuando la última liga a un archivo desaparece, entonces el sistema borra el nodo-i, y en
consecuencia, también el archivo.
El propósito de una liga es de dar dos nombres a un mismo archivo, para que éste
pueda aparecer en dos directorios diferentes.
Puede haber ligas suaves o duras. La liga suave se utiliza cuando se hace un ligado a
un archivo que se encuentra en un sistema de archivos diferente. El ligado también
puede hacerse entre directorios.
Ejercicio 1
Cree el directorio en ~/SistArchivos/ , guarde en dicho directorio el siguiente shell
scripting con el nombre “preparacion.sh” y ejecutelo:
#!/bin/bash
mkdir originales respaldo
mkdir originales/Nivel
mkdir originales/Nivel/Segundo
cat /etc/passwd > originales/Listado
wget http://cs.mty.itesm.mx/lab/operativos/img_tec/thumb_3.png
wget http://cs.mty.itesm.mx/lab/operativos/Complementos/Practica7.tar
mv thumb_3.png originales/thumb_3.png
mv Practica7.tar originales/Practica7.tar
ls / -R > originales/Nivel/Segundo/Listado.txt
ln originales/Listado respaldo/Arch1
ln -s ../originales/thumb_3.png respaldo/Arch2
ln originales/thumb_3.png respaldo/Arch3
Ejercicio 2
Guarde el archivo tree.c en ~/SistArchivos/ y a continuación compílelo (nombre de
salida: Tree) para después ejecutarlo utilizando como directorio objetivo el directorio
~/SistArchivos/ de tal forma que despliegue la información de cada archivo.
Ejercicio 3
Guarde el archivo estado1.c en ~/SistArchivos/ y a continuación compilelo (nombre de
salida: Estado1) para después ejecutarlo utilizando como argumento
~/SistArchivos/originales/
Ejercicio 4
Guarde el archivo copiar.c en ~/SistArchivos/ y a continuación compílelo (nombre de
salida: Copiar)
rm ~/SistArchivos/originales -f -R
Conclusiones
Mencione sus conclusiones personales respecto a sistemas de archivos en
GNU/Linux.
Laboratorio
Acceso directo a la liga de formulario: SISTEMA DE ARCHIVOS
Referencias:
Love, Robert., Linux System Programming, O'REILLY, 2007. Biblioteca: QA76.76.O63 L69
2007
Marquez , Fernando, Programación Avanzada en UNIX, 3a. edicion, 2004. Biblioteca QA,
76.76, .O63, M37, 2006 (Reserva)
Complement
Práctica
Dispositivos.
…
Como se dijo, los dispositivos se manejan como archivos; un ejemplo es el teclado.
El teclado es un dispositivo orientado a transferencia por caracter. El disco
duro es un ejemplo de dispositivo orientado a bloque. Los dispositivos orientados
a bloque son aquellos que necesitan mover una gran cantidad de datos en una sola
operación.
Por ejemplo, observe en el siguiente comando que requiere datos del teclado:
[user@localhost home]$ cat > tmpf
Hola
Este es un ejemplo
CTRL+D
que mientras esté en el mismo renglón (datos en el buffer) puede regresare con
backspace; pero una vez oprime RETURN (se vacía el buffer), ya no puede regresar
al renglón anterior (ya que la información ya no está en el buffer).
Con las llamadas al sistema ioctl() es posible cambiar las características de los
dispositivos. Como se dijo anteriormente el teclado es un dispositivo orientado a
transmisión por caracter. Utilizando la llamada al sistema ioctl, podemos cambiar
de modo de transferencia de los datos por caracter al modo de transferencia por
buffer. A continuación se presenta el código que hace esta tarea:
Tanto REQUEST como ARG dependen del tipo de dispositivo que queremos
controlar.
Ejemplo
El siguiente programa muestra la forma de uso de la llamada al sistema ioctl(). Al
ejecutarlo, suprime el ECO del teclado en la consola (no muestra las teclas que se
presionan) y no espera la tecla ENTER para leer los datos del teclado:
/***
PROGRAMA; entrada.c
DESCRIPCION: Programa de ejemplo para ilustrar el empleo de
ioctl sobre terminales.
***/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <termio.h>
#define BS 127 /* Back space. Normalmente tiene asociado el ascii 8.
*/
/***
FUNCION: leer_cadena
DESCRIPCION: Esta funcion lee de la entrada estandar una cadena
cuya longitud máxima no debe superar los n caracteres.
***/
char *leer_cadena(int n)
{
char *buffer, c;
int i = 0;
struct termio parametros_ant;
struct termio parametros;
return (buffer);
}
main()
{
char *str;
int longitud;
char *leer_cadena ();
str = leer_cadena(longitud);
printf ("\n%s \n",str);
}
Con las opciones (ECHO), seria la misma salida pero pero lo que vamos
escribiendo en el teclado no aparece en pantalla hasta que oprimimos
<RETURN>.
se puede observar que hay echo el presionar una tecla pero en realidad solo
se lee un dato.
Ratón
Otro dispositivo del que podemos obtener información fácilmente es el ratón. Este
dispositivo es accesible mediante la ruta /dev/input/mice . Si el archivo mice no te
entrega datos puedes intentar con mouse0, mouse1, etc.
El ratón es un dispositivo de tipo caracter. Los datos que entrega este dispositivo
tiene el siguiente formato:
A continuación se muestra el programa dumpmice que despliega en pantalla la
información leída de dicho dispositivo.
/* PROGRAMA: dumpmice.c
FUNCION:
Imprime los datos que entrega el dispositivo
/dev/input/mice
USO:
./dumpmice
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
struct MiceStruct
{
unsigned char mode;
char x;
char y;
};
int main()
{
unsigned char ii = 0x10;
unsigned char m = 0x00;
char x,y;
struct MiceStruct data; // Struct to store data.
int fd = 0;
struct termios param;
// Opens device.
if ((fd = open("/dev/input/mice",O_RDONLY)) == -1)
{
perror("Error al abrir el dispositivo:
/dev/input/mice");
return -1;
}
while (1)
{
// Gets data from device.
if ( read(fd, &data, sizeof(struct MiceStruct)) < 0)
{
printf("Error leyendo datos\n");
perror("Read");
break;
}
m = data.mode;
//x = (int)data.x;
//y = (int)data.y;
x = data.x;
y = data.y;
printf("|%d\t,%d\t|%d\t,%d\t|%d\t|%d\t|%d\t|%d\t|%d\
t|%d\t|\n",
(m&0x80)&&1,(m&0x40)&&1,(m&0x20)&&1,(m&0x10)&&1,(m&0
x08)&&1,(m&0x04)&&1,(m&0x02)&&1,(m&0x01)&&1,
x, y);
ii++;
param.c_lflag |= ICANON|ECHO;
tcsetattr(0, TCSANOW, ¶m);
close (fd);
return 0;
}
Referencias:
Love, Robert. Linux System Programming, O'REILLY, 2007. Biblioteca:
QA76.76.O63 L69 2007
Kurt Wall. Programación en Linux con Ejemplos. 2000. Prentice Hall. Biblioteca:
QA76.76.O63 W35718 2000 (Reserva)
Laboratorio:
Formulario del laboratorio
Laboratorio de
Sistemas Operativos
ÍNDICE
Objetivo
Conceptos
Comunicación entre procesos
Alcance de una red de área personal
Sockets
Arquitectura/modelo Cliente-Servidor.
Cliente-Servidor Unix.
Ejemplo de socket local (de la familia AF_UNIX)
Ejemplo de socket remoto (de la familia AF_INET)
Anexo A – Llamadas a sistema utilizadas en la arquitectura cliente-servidor
Laboratorio
Objetivos
Siguiendo con los elementos de programación en un ambiente operativo GNU/Linux,
trabajarás en esta ocasión con los mecanismos utilizados por aplicaciones del tipo
“Cliente/Servidor”, utilizando protocolos de comunicación denominados “AF_UNIX”,
y “TCP/IP” (AF_INET)
Conceptos
Comunicación entre procesos
Hoy en día es fundamental que un proceso pueda comunicarse con otros (incluyendo
procesos entre aplicaciones de usuarios y entre procesos del propio S.O.) A este tipo
de comunicación se le denomina IPC por su escritura en inglés (“Inter-Process
Communication”).
Puede llevarse a cabo de diferentes formas, por ejemplo compartiendo un espacio
específico de memoria RAM (donde se almacenan variables o buffers compartidos) y
los accesos a esas áreas compartidas se realizan mediante las rutinas de IPC. Estas
rutinas proveen los mecanismos necesarios para que la comunicación sea siempre
sincronizada y exitosa (sin riesgo de una “Race condition” o problemas similares).
Este tipo de comunicación se requería desde antes de que una computadora
ocupará comunicarse con otra computadora y por lo mismo existe una familia de
protocolos en los sistemas UNIX que son heredados en los sistemas operativos
GNU/Linux. A esta familia se le denomina “AF_UNIX” y se utiliza para comunicar
procesos que residen en una misma máquina.
Una red de área personal (Personal Area Network or PAN) es una red de
computadoras utilizada para la comunicación entre los dispositivos
electrónicos (teléfonos inteligentes, tabletas, computadoras) cercanos a una
persona. Los dispositivos pueden no necesariamente pertenecer a la persona
en cuestión.
Una red inalámbrica de área personal (WPAN) se puede también hacer posible con
tecnologías de red tales como IrDA, Bluetooth, UWB, Z-Wave y ZigBee. La
diferencia clave entre cada una de ellas es el consumo que exigen.
La siguiente figura indica algunos de los protocolos utilizados por Personal, Local,
Metropolitan and Wide area networks:
Sockets
En los orígenes de Internet, surgió la necesidad de intercomunicar procesos entre sí,
por lo que la Universidad de Berkeley propuso la primera especificación e
implementación de los sockets.
El socket es una interfaz que se compone de una serie de llamadas a sistema y que
permite que dos procesos (posiblemente situados en computadoras
distintas) intercambien un flujo de datos de forma fiable y ordenada. El elemento clave
del concepto esta en que los programas puedan comunicarse entre sí. Es necesario
que un programa sea capaz de localizar al otro y que ambos puedan intercambiar
cualquier secuencia de datos.
#include <sys/types.h>
#include <sys/socket.h>
int socket(int af, int type, int protocol);
Esta llamada a sistema regresa un file descriptor (fd), mediante el cual se puede
acceder a dicho socket. Recordemos que los fd’s pueden utilizarse no únicamente
para escribir y leer archivos, sino también para enviar y recibir datos entre procesos
El parámetro af (address family) especifica la familia de protocolos que se
desea utilizar. Dos de estas familias, presentes en la mayoría de los sistemas,
son:
Arquitectura/modelo Cliente-Servidor.
Esta arquitectura consiste en un proceso (Cliente) que realiza peticiones a otro
proceso (Servidor), y este último le responde. Esta idea se puede aplicar a tanto a
procesos que se ejecutan en la misma computadora como a aquellos que se
encuentran corriendo en diferentes computadoras conectadas por una red.
Cliente-Servidor Unix.
Para implementar un esquema de comunicación cliente-servidor entre procesos, se
utilizan diversas llamadas al sistema, tales como write() o read(). Si te
resultan familiares es debido a que en en Unix “everything is a file”, asi que la
comunicación entre dos computadoras distintas también se lleva a cabo por medio de
un “File” que en realidad es un canal de comunicación entre las dos procesos. En el
siguiente diagrama (Fig. 2) puedes observar la secuencia de llamadas a sistema
usadas para una comunicación orientada a conexión:
Observa las llamadas a sistema que se utilizan, en particular la creación del socket
donde se especifica la familia de direcciones de protocolos del socket y su semántica:
socket(AF_UNIX, SOCK_STREAM, 0);
/*
* client1.c - a simple local client program.
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/un.h>
#include <unistd.h>
int main()
{
int sockfd;
int len;
struct sockaddr_un address;
int result;
char ch = 'A';
/* Create a socket for the client. */
sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
if(result == -1) {
perror("oops: client1");
exit(1);
}
/*
* server1.c - a simple local server program.
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/un.h>
#include <unistd.h>
int main()
{
int server_sockfd, client_sockfd;
int server_len, client_len;
struct sockaddr_un server_address;
struct sockaddr_un client_address;
/* Remove any old socket and create an unnamed socket for the server. */
unlink("server_socket");
server_sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
printf("server waiting\n");
/* Accept a connection. */
client_len = sizeof(client_address);
client_sockfd = accept(server_sockfd,
(struct sockaddr *)&client_address, &client_len);
/* We can now read/write to client on client_sockfd. */
read(client_sockfd, &ch, 1);
ch++;
write(client_sockfd, &ch, 1);
close(client_sockfd);
}
}
/*
* client2.c - a simple network client program.
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
int main()
{
int sockfd;
int len;
struct sockaddr_in address;
int result;
char ch = 'A';
if(result == -1) {
perror("oops: client2");
exit(1);
}
/* We can now read/write via sockfd. */
write(sockfd, &ch, 1);
read(sockfd, &ch, 1);
printf("char from server = %c\n", ch);
close(sockfd);
return 0;
}
/*
* server2.c - a simple network server program.
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
int main()
{
int server_sockfd, client_sockfd;
int server_len, client_len;
struct sockaddr_in server_address;
struct sockaddr_in client_address;
printf("server waiting\n");
/* Accept a connection. */
client_len = sizeof(client_address);
client_sockfd = accept(server_sockfd,
(struct sockaddr *)&client_address, &client_len);
Anexo A
Llamadas a sistema utilizadas en la arquitectura cliente-
servidor
A continuación, una lista con las llamadas a sistema utilizadas en el proceso de
conexión por sockets, que son parte de la API definida por la librería de sockets de
Berkeley:
domain, which specifies the protocol family of the created socket. For
example: PF_INET for network protocol IPv4 or PF_INET6 for IPv6.
PF_UNIX for local socket (using a file).
bind() - is typically used on the server side, and associates a socket with a
socket address structure, i.e. a specified local port number and IP address.
Assigns a socket an address. When a socket is created using socket(), it is only
given a protocol family, but not assigned an address. This association with an
address must be performed with the bind() system call before the socket can
accept connections to other hosts. bind() takes three arguments:
listen() - is used on the server side, and causes a bound TCP socket to enter
listening state. After a socket has been associated with an address, listen()
prepares it for incoming connections. However, this is only necessary for the
stream-oriented (connection-oriented) data modes, i.e., for socket types
(SOCK_STREAM, SOCK_SEQPACKET). listen() requires two arguments:
sockfd, the descriptor of the listening socket that has the connection
queued.
The accept() function returns the new socket descriptor for the accepted
connection, or -1 if an error occurs. All further communication with the remote
host now occurs via this new socket. Datagram sockets do not require
processing by accept() since the receiver may immediately respond to the
request using the listening socket. Prototype:
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *cliaddr,
socklen_t *addrlen);
connect() - is used on the client side, and assigns a free local port number to
a socket. In case of a TCP socket, it causes an attempt to establish a new TCP
connection. The connect() system call connects a socket, identified by its file
descriptor, to a remote host specified by that host's address in the argument list.
Certain types of sockets are connectionless, most commonly user datagram
protocol sockets. For these sockets, connect takes on a special meaning: the
default target for sending and receiving data gets set to the given address,
allowing the use of functions such as send() and recv() on connectionless
sockets.
connect() returns an integer representing the error code: 0 represents success,
while -1 represents an error. Prototype:
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr
*serv_addr, socklen_t addrlen);
type specifies the address family type (e.g., AF_INET) of the host
address.
The functions return a NULL pointer in case of error, in which case the external
integer h_errno may be checked so see whether this is a temporary failure or an
invalid or unknown host. Otherwise a valid struct hostent * is returned.These
functions are not strictly a component of the BSD socket API, but are often used
in conjunction with the API functions. Furthermore, these functions are now
considered legacy interfaces for querying the domain name system. New
functions that are completely protocol-agnostic have been defined.
These new function are getaddrinfo() and getnameinfo(), and are based on a
new addrinfo data structure. Prototypes:
struct hostent *gethostbyname(const char *name);
struct hostent *gethostbyaddr(const void *addr, int
len, int type);
char* char - Recibe una cadena de caracteres; para que no batallen, utilicen un
arreglo del tipo Char. La longitud del arreglo debe estar indicado también en "size_t
size" (que es el tamaño del buffer de datos que se va a enviar). No olviden que
tendrán que utilizar ampersand ( & ) en la función de read ya que el buffer leído se
depositará en "char* char)
ejemplo. read(client_sockfd, &Char, sizeof(Char))
Ejercicio 2
Compile los códigos de cliente y servidor local, ejecute el servidor en el fondo y ejecute
un par de clientes en la misma terminal.
Ejercicio 3
Confirme el funcionamiento del servidor mediante Netstat
Netstat es una herramienta incluida en sistemas GNU/Linux la cual permite revisar
qué recursos están separados para la comunicación entre procesos de AF_UNIX y
para la comunicación con TCP/IP (AF_inet). Si se ejecuta el comando sin argumentos
se recibirá una gran cantidad de información, por lo tanto tenemos que elaborar algo
de filtrado.
Como el objetivo es validar que una aplicación servidor para la familia AF_UNIX está
registrado y en estado de escucha (Listening), se espera que verifiques (man….) qué
argumentos te permiten desplegar los servicios con esas características.
Seguramente saldrán múltiples servicios, la mayoría internos del S.O. por lo que debes
asegurar que la salida esté filtrada (grep?) y solo muestre el proceso que nos
interesa. Para facilitar esto último puedes extraer el PROCESS-ID del servidor de
antemano (comando $ ps con ciertos argumentos…) y utilizarlo en este punto.
$ ./Servidor
10.17.112.90: Mensaje 1
10.17.112.70: Mensaje 2
10.17.112.80: Mensaje 3
$ ./Cliente Mensaje1
Servidor: Recibido
Entregas:
Entregar el código fuente del servidor ya modificado.
Entregar el código fuente del cliente ya modificado.
Entregar un ejemplo de corrida del servidor y dos o más clientes.
COMPLEMENTO 3
I.Antecedentes
II.Bluetooth.
i.Conceptos esenciales de la programación Bluetooth.
ii.Cliente-Servidor Bluetooth.
III. Wiimote.
IV.Cliente-Servidor Wiimote.
V.Librería CWiid
VI.Anexo A
VII.Anexo B.
VIII.Referencias
IX.FAQ:
X.Laboratorio
i.Librería CWiid.
i.Prerequisitos
ii.Comprobar el correcto funcionamiento de la librería cwiid.
ii. Cliente-Servidor Wiimote.
iii.Conectarse a una PC por Bluetooth y hacer "dump" de un "ping" (l2ping)
XI. Reporte segun lo que se le pida en el formulario
Antecedentes
Esta es la continuación de la practica de Cliente-Servidor.Se puede acceder a ella desde
esta liga.
Bluetooth.
La clasificación de los dispositivos Bluetooth como "Clase 1", "Clase 2" o "Clase 3"
es únicamente una referencia de la potencia de trasmisión del dispositivo, siendo
totalmente compatibles los dispositivos de una clase con los de la otra.
Cliente-Servidor Bluetooth.
NOTA: Si tiene problemas compilando revise primero los pre-requisitos del laboratorio.
/*
* rfcomm-client.c – sends a message to a BT device.
*/
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/rfcomm.h>
// allocate a socket
s = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
// connect to server
status = connect(s, (struct sockaddr *)&addr, sizeof(addr));
// send a message
if( 0 == status ) {
status = send(s, "hello!", 6, 0);
}
close(s);
return 0;
}
// allocate socket
s = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
// close connection
close(client);
close(s);
return 0;
}
/*
* rfcomm-server2.c – receives a message from a BT device.
*/
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/rfcomm.h>
// allocate socket
s = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
while (1)
{
// accept one connection
client = accept(s, (struct sockaddr *)&rem_addr, &opt);
// close connection
close(client);
}
close(s);
return 0;
}
Wiimote.
“The Wii Remote, sometimes unofficially nicknamed "Wiimote", is the primary
“It contains a 1024x768 infrared camera with built-in hardware blob tracking of
up to 4 points at 100Hz. This significantly out performs any PC "webcam" available
today. It also contains a +/-3g 8-bit 3-axis accelerometer also operating at 100Hz
and an expansion port for even more capability” [1].
Cliente-Servidor Wiimote.
El esquema de trabajo que se propone es: una computadora actúa como servidor
que pide reiteradamente (polling) información al cliente, que en este caso será el
control Wiimote. La información que entrega el cliente corresponde al estado de
los botones del control Wiimote.
HCI (Host Controller Interface) provee una interfaz uniforme para accesar a las
capacidades del hardware Bluetooth. Usando HCI una aplicación puede accesar el
hardware de Bluetooth sin necesidad de conocer detalles sobre la implementación
del hardware o de la capa de transporte. HCI provee algunas herramientas para
interactuar con un adaptador Bluetooth, entre ellas se encuentran hciconfig, que
sirve para configurar dispositivos Bluetooth y obtener información del mismo, y
hcitool, que se usa para configurar conexiones Bluetooth, entre otras.
Otra herramienta, hcidump, nos permite vaciar los datos que un dispositivo con
interfase Bluetooth entrega al driver HCI. En el Anexo B se muestra una versión
reducida de la herramienta hcidump donde se muestra explícitamente la
comunicación con el dispositivo utilizando sockets, en un esquema no orientado a
conexión. En la Figura 2 puedes ver la secuencia de llamadas a sistema utilizada en
la comunicación con el control Wiimote.
Liga socket
bind() con bind()
dirección
Petición de
servicio
recepción de mensajes por
recvmsg() sendmsg()
sondeo(polling)
Librería CWiid
http://www.wiili.org/index.php/CWiid
http://www.cwiid.org (ó http://abstrakraft.org/cwiid)
https://help.ubuntu.com/community/CWiiD
http://www.circuitdb.com/articles/7/3
Anexo A
Esta liga es un tema de referencia, maneja el concepto cliente-servidor para dentro de
una computadora, por medio de TCP/IP y por medio de blueetooth.
https://docs.google.com/Doc?docid=0Ae4E85DTQTe0ZGhuZmZmc182MmRzem42N3
hm&hl=es
Anexo B.
/*
* hciwiidump.c
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2000-2002 Maxim Krasnyansky <[email protected]>
* Copyright (C) 2003-2007 Marcel Holtmann <[email protected]>
*
*
* 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
/* Modificado para hacer más claro el proceso de obtención de datos de un
* control Wiimote mediante el protocolo Bluetooth
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <sys/poll.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
#include "parser/parser.h"
/* Default options */
static int snap_len = SNAP_LEN;
static int permcheck = 1;
struct hcidump_hdr {
uint16_t len;
uint8_t in;
uint8_t pad;
uint32_t ts_sec;
uint32_t ts_usec;
} __attribute__ ((packed));
#define HCIDUMP_HDR_SIZE (sizeof(struct hcidump_hdr))
struct btsnoop_pkt {
uint32_t size; /* Original Length */
uint32_t len; /* Included Length */
uint32_t flags; /* Packet Flags */
uint32_t drops; /* Cumulative Drops */
uint64_t ts; /* Timestamp microseconds */
uint8_t data[0]; /* Packet Data */
} __attribute__ ((packed));
#define BTSNOOP_PKT_SIZE (sizeof(struct btsnoop_pkt))
if (sock < 0)
return -1;
dh = (void *) buf;
dp = (void *) buf;
frm.data = buf + hdr_size;
ctrl = malloc(100);
if (!ctrl) {
free(buf);
perror("Can't allocate control buffer");
return -1;
}
if (dev == HCI_DEV_NONE)
printf("system: ");
else
printf("device: hci%d ", dev);
memset(&msg, 0, sizeof(msg));
fds[nfds].fd = sock;
fds[nfds].events = POLLIN;
fds[nfds].revents = 0;
nfds++;
while (1) {
// poll() revisa si un conjunto de file descriptors para ver si alguno
// de ellos esta listo para una operacion de I/O
int i, n = poll(fds, nfds, -1);
if (n <= 0)
continue;
iv.iov_base = frm.data;
iv.iov_len = snap_len;
msg.msg_iov = &iv;
msg.msg_iovlen = 1;
msg.msg_control = ctrl;
msg.msg_controllen = 100;
frm.ptr = frm.data;
frm.len = frm.data_len;
parser.state=0;
// imprime la informacion
if (parser.flags & DUMP_RAW)
raw_ndump(0, &frm,-1);
else
hci_dump(0, &frm);
fflush(stdout);
}
return 0;
}
opt = 1;
if (setsockopt(sk, SOL_HCI, HCI_DATA_DIR, &opt, sizeof(opt)) < 0) {
perror("Can't enable data direction info");
return -1;
}
opt = 1;
if (setsockopt(sk, SOL_HCI, HCI_TIME_STAMP, &opt, sizeof(opt)) < 0) {
perror("Can't enable time stamp");
return -1;
}
/* Setup filter */
hci_filter_clear(&flt);
hci_filter_all_ptypes(&flt);
hci_filter_all_events(&flt);
if (setsockopt(sk, SOL_HCI, HCI_FILTER, &flt, sizeof(flt)) < 0) {
perror("Can't set filter");
return -1;
}
return sk;
}
argc -= optind;
argv += optind;
optind = 0;
return 0;
}
Referencias
Lee, Johnny. Wii. http://www.cs.cmu.edu/~johnny/projects/wii/
CWiid. http://abstrakraft.org/cwiid
Wikipedia. Wiimote. http://en.wikipedia.org/wiki/Wiimote
FAQ:
Error: No se encuentra el archivo Python.h
Solución: Instalar el paquete python-dev (ubuntu) ó python-devel (fedora)
Nota: Instalar la versión de python mas reciente.
Laboratorio
En sintensis, se manejaran dos librerias distintas, CWiid y hciwiidump, ambas para el
desarrollo de la práctica. La primera se trata de la libreria para manipulación del control
del Wii y la segunda es un sniffer - analizador de tráfico - pensado para el Wiimote
Librería CWiid.
Prerequisitos
libbluetooth2(o superior )
bluez-utils
original-awk
bison
flex
libbluetooth2-dev
autoconf
mouseemu
libgtk2.0-dev
python (Para desarrollo en C - dev - )
make
gcc
Esto puede hacerse de manera gráfica vía un administrador de paquetes
(estilo Synaptics) o mediante la línea de comandos (un solo comando en
modo súper-usuario):
su
yum install flex bison
yum install libbluetooth-dev
yum install libgtk2.0-dev
yum install cwiid lswm wmgui wminput
yum install bluez-libs bluez-libs-devel gtk2-devel
yum install python python-devel
su
yum search python
yum search bluez
yum search libbluetooth
wget http://abstrakraft.org/cwiid/downloads/cwiid-0.6.00.tgz
cd cwiid-0.6.00/
./configure
make
sudo make install
cd wmdemo
gcc -o wmdemo wmdemo.c -lcwiid
wmgui
hcitool scan
Scanning:
<dirección> Nintendo RVL-CNT-01
wmgui <dirección>
./wmdemo <dirección>
Cliente-Servidor Wiimote.
En esta sección leerás el estado de cada uno de los botones del Wiimote, primero
crearás una conexión con el control mediante un driver HID y después “vaciarás” el
contenido de la información que el control le manda al driver.
1 - Cambiar a súper usuario (fedora) o ejecutar con sudo (Ubuntu).
2 - Buscar el dispositivo y obtener su direccion(bdaddr) :
Presionar simultáneamente los botones 1 y 2 del wiimote.
[root@localhost bluez-hcidump-1.42]# hcitool scan
--> 00:19:1D:BC:3F:07 Nintendo RVL-CNT-01
3 - Conectarse al dispositivo mediante un driver HID:
Sintaxis: hidd --connect <bdaddr>
Presionar simultáneamente los botones 1 y 2 del wiimote.
hidd --connect 00:19:1D:BC:3F:07
4 - Descarga el archivo hciwiidump.tar.gz . (Comando Wget)
5 - Descomprime el archivo
tar -xvzf hciwiidump.tar.gz
6 - Entra al directorio expandido.
7 - Entra al subdirectorio src .
8 - Pega el código del Anexo B (hciwiidump.c) dentro del directorio src en un
archivo con nombre hcidump.c . El código del Anexo B, es una versión reducida, de
la aplicación hcidump original, con el fin de mostrar claramente el uso de los
sockets.
9- Ejecute un make en la carpeta scr , si aparece un error parecido a la leyenda "
hcidump.c:52:24: error: redefinition of ‘ntoh64’" Abra el archivo hcidump.c y
comente la estructura ntoh64 y vuelva a ejecutar el make
ÍNDICE
1. Seguridad en informática.
1. Vulnerabilidades, Amenazas, Ataques y Controles.
2. Programación No segura
1. ¿Y esto en qué me afecta?
3. Tipos de Riesgos en un sistema
1. Errores de Diseño, Implementación y Operación
2. ¿Que busca un atacante de mi equipo de computo?
3. Los recursos pueden ser de diversas índoles pero los principales son:
4. ¿Qué son las botnets?
5. ¿Cómo defenderse de estos Ataques?
2. Nmap.
1. Sintáxis
2. Ejemplos.
3. Nessus
1. Instalación de Nessus 5
4. FAQ:
5. Referencias:
6. Laboratorio:
1. Nmap.
2. Nessus
Seguridad en informática.
Cualquier parte de un sistema computacional puede ser el objetivo de un crimen. Un
sistema computacional se puede ver como una colección de hardware, software,
medio de almacenamiento, datos y personas que una organización usa para realizar
una tarea de computo. A veces, asumimos que las partes de un sistema
computacional no tienen valor para un intruso, pero a menudo nos equivocamos. Por
ejemplo, tendemos a pensar que los bienes más valiosos en un banco son el dinero, el
oro y la plata en la bóveda. Pero de hecho la información del cliente dentro de la
computadora del banco puede ser más valiosa. Almacenada en papel, grabada en un
medio de almacenamiento, residente en memoria, o transmitida por la líneas de
teléfono o por enlaces satelitales, esta información puede ser usada en miles de
formas para ganar dinero de forma ilícita. Un banco de la competencia podría usar
esta información para robar clientes o incluso interrumpir el servicio y desprestigiar al
banco. Un individuo sin escrúpulos podría transferir dinero de una cuenta a otra sin el
permiso del propietario. Un grupo de estafadores podría contactar una gran número de
depositantes y convencerlos de invertir en planes fraudulentos. La variedad de
objetivos y ataques vuelve la seguridad informática muy difícil.
Cualquier sistema es más vulnerable en su punto más débil. Un ladrón que intente
robar algo de tu casa no intentará penetrar una puerta metálica de cinco centímetros
de ancho si una ventana le da un acceso más fácil. Del mismo modo, un sistema
sofisticado de seguridad física perimetral no compensa el acceso sin vigilancia a
través de una simple línea telefónica y un MODEM. Esta idea es uno de los principios
de la seguridad computacional.
Para ver la diferencia entre una amenaza y una vulnerabilidad vea la Figura 1; en ella
se puede ver una pared conteniendo el agua. El agua a la izquierda de la pared es una
amenaza para el hombre a la derecha de la pared; el agua podría aumentar de nivel,
inundando al hombre, o podría permanecer por debajo de la altura de la pared y
causar que la pared collapse. Así que la amenaza de daño es el potencial para el
hombre de mojarse, lastimarse o ser ahogado. Por ahora, la pared está intacta, por lo
que la amenaza para el hombre no está realizada.
Sin embargo, podemos ver una pequeña fractura en la pared, una vulnerabilidad que
amenaza la seguridad de la persona. Si el agua se eleva al nivel (o más allá) de la
fractura, explotará la vulnerabilidad y dañará a la persona.
¿Cómo podemos abordar estos problemas? Usamos un control como una medida
de protección. Un control es una acción, un dispositivo, un procedimiento o una técnica
que remueve o reduce una vulnerabilidad. En la Figura 1, la persona puede poner un
dedo en un hoyo y así controlar la amenaza de que el agua se fugue, hasta que
encuentre una solución al problema más permanente. En general, podemos describir
la relación entre amenazas, controles y vulnerabilidades de esta forma: Una amenaza
se bloquea por un control de una vulnerabilidad.
Los daños ocurren cuando una amenaza se lleva a cabo contra una vulnerabilidad.
Para proteger contra el daño, entonces, podemos neutralizar la amenaza, cerrar la
vulnerabilidad, o ambos. La posibilidad de que un daño ocurra se conoce como
riesgo. Podemos hacer frente a los daños de diferentes maneras. Podemos tratar de:
Prevenir, bloquear el ataque o cerrar la vulnerabilidad,
Disuadir, hacer el ataque más difícil pero no imposible,
Desviar, volver otro objetivo más atractivo, o este no tanto,
Detectar, cuando sucede o algún tiempo después,
Recuperar de sus efectos.
Programación NO segura
Aprender a programar es un arte, pero saber programar con metodologías y auditorias
de seguridad es un reto que debe ser obligatorio en nuestros días y sin embargo
sigue teniendo baja penetración. Como una comprobación del actual estado, el
instituto ISACA (www.isaca.org ) realizó una encuesta a inicio del 2012 a mas de 20
mil desarrolladores de aplicaciones y mas de 6 mil auditores de seguridad obteniendo
resultados interesantes, tales como:
79% De los desarrolladores no manejan ningún esquema de seguridad en sus
códigos o bien manejan esquemas ad-hoc para la creación de aplicaciones.
Ambos grupos opinan fuertemente que el modelo de construcción de software
SDLC (System Development Life Cycle) no incluye fases específicamente para
validar la seguridad.
30% de los desarrolladores crean o aplican los modelos de seguridad una vez
que la aplicación ha sido publicada.
51% de los desarrolladores consideran que no estan entrenados para detectar
y/o corregir fallas de seguridad en el código de las aplicaciones.
Más de la mitad de los desarrolladores consideran que reparar bugs o parchar
los programas es un consumo de recursos de la empresa desperdiciado.
Una parte significativa de las auditorías de seguridad se enfocan a la
comunicación entre aplicaciones y no propiamente a la capa Aplicación.
En SANS.org se maneja un listado de los errores mas severos que puede sufrir el
código fuente de una aplicación (http://www.sans.org/top25-software-errors/ ). Dicho
listado es actualizado cada año para tomar en cuenta aquellos elementos que están
siendo más explotados. Investiga en la web en qué consiste cada uno que no
conozcas:
¿Qué tan normal es el escenario anterior en estos días? Bastante normal; los exploits
menos conocidos y por lo tanto que más probabilidad tienen de tener éxito se pueden
cotizar en miles de dólares. Otro caso conocido es el de el gobierno estadounidense
que pagó un cuarto de millón de dolares por un exploit en iOS para los teléfonos
inteligentes Iphone. Estas ventas se hacen por intermediarios , pero de acuerdo a
Forbes la siguiente tabla (Fig. 4) muestra un estimado de precios actuales (2012).
Figura 4 - Precios en dólares de venta de vulnerabilidades no conocidas
Figura 5 - Los ataques modernos no son meramente hechos por "aburrimiento" pero si
se intentan volver "sencillos"
El siguiente listado son las técnicas con las cuales se puede atacar y/o espiar a una
máquina en particular.
1. Correo electrónico - una vía de acceso fácil para virus y caballos de troya, ya
sea que viajen ocultos en el correo o bien infectan archivos adjuntos de los
correos. El riesgo de contagio viene que el programa para leer los correos debe
abrir el correo y entonces puede ocurrir la infección. La mejor técnica es el
empleo de antivirus y firewalls, además del sentido común de no abrir correos
sospechosos.
2. Navegación por servidores web - Se puede llegar a páginas que utilicen
código Java o controles ActiveX, los cuales son muy empleados para la creación de
código malicioso que se ejecutan directamente desde el equipo atacado. Por
supuesto, la descarga de software contaminado (código original con porciones
maliciosas embebidas a él) afectan de forma similar al caso 1.
Los Sistemas operativos abiertos (como GNU/Linux) tienen agujeros mas conocidos y
controlados que aquellos que existen en sistemas operativos cerrados (como
Windows, MAC OS X, iOS). La importancia (y ventaja) del código abierto radica en
miles de usuarios que analizan dicho código en busca de posibles bugs y ayudan a
obtener soluciones en forma inmediata.
Se pueden apreciar puertas traseras, las cuales serían el primer paso para un ataque
escalonado. Los más importantes son los troyanos auto-descargables que tuvieron un
impacto en Noviembre. Entre los malwares se incluyen Flashback y Devilrobber.
En general, la única razón por la que estos sistemas tenían pocas amenazas en el
pasado era por su baja penetración en el mercado. En este aspecto Android es un
claro ejemplo. En el 2010 se tenía no más del 1% de los malware hacia dispositivos
móviles identificados para dicho S.O., pero para el 2011 Android paso a sufrir casi la
mitad del Malware en dichos medios, esto debido a su alta penetración en el mercado.
En síntesis, ningún S.O. es libre de vulnerabilidades y dependiendo de su uso es que
tanto se intenta explotarlas y en cuanto tiempo y de qué magnitud resultan las
explotaciones.
Figura 7 - Malware (versiones únicas) para móviles detectados por S.O. móvil
Los recursos pueden ser de diversas índoles pero los principales son:
Las redes de bots o Botnet, es una red o grupo de ordenadores infectados por bots y
controlados remotamente por el propietario de los bots. Este propietario da
instrucciones que pueden incluir: la propia actualización del bot, la descarga de una
nueva amenaza, mostrar publicidad al usuario, el envío de spam o el lanzar ataques
de denegación de servicio, entre otras. Generalmente, al ordenador infectado se le
denomina zombi. Algunas redes pueden llegar a tener decenas de miles de
ordenadores zombis bajo control remoto.
Figura 8 - Botnet (Panda Security)
No asuma que los botnets son problemas solamente de PC, cualquier dispositivo en
Internet o una red grande (por los recursos que esta maneja) es un potencial zombie,
se han detectado Botnets en Android, Linux, Windows, PS3, Servidores WEB
legítimos, celulares vía SMS, y un sinfín de etc…
La solución más accesibles en cada caso es mantenerse informado sobre todos los
tipos de ataques existentes y las actualizaciones que permanentemente lanzan las
empresas desarrolladoras de software, principalmente de sistemas operativos. Las
siguientes son medidas preventivas que toda red y administrador deben conocer y
desplegar cuanto antes:
Nmap.
Nmap es un escáner de seguridad, que permite descubrir computadoras y
servicios para crear un mapa de la red, es capaz de descubrir servicios pasivos,
aunque estos no se anuncien. Permite determinar varias características de los
sistemas remotos, como: el sistema operativo, el tipo de dispositivo, el tiempo de
actividad, el software que proporciona el servicio y su versión exacta, entre otras
características. Su página oficial es: Nmap
Nmap puede ser utilizado para detectar vulnerabilidades en una red y prevenir que
éstas sean aprovechadas por alguna persona; los administradores de sistemas lo
utilizan generalmente para detectar servidores no autorizados y computadoras que no
cumplan con los requisitos mínimos de seguridad de una organización.
Instalación
Se puede utilizar apt para su instalación (apt-get install nmap) pero usualmente se
trata de versiones anteriores. Si decide instalar la ultima versión para Linux este
tutorial de nmap.org le será util.
Sintaxis
La sintaxis para ejecutar Nmap desde la línea de comandos es la siguiente:
nmap [ <tipo-de-búsqueda> ...] [ <opciones> ] { <especificación-del-
objetivo> }
dónde:
Cuando se ejecuta Nmap desde la línea de comandos, todo lo que no es una opción (o
un argumento de opción) es tratado como una especificación de objetivo. El caso más
simple es especificar una dirección IP o un nombre de dominio a explorar.
En algunas ocasiones se desea explorar una red completa de equipos adyacentes.
Para esto, Nmap soporta direccionamiento estilo CIDR (Classless Inter-Domain
Routing). Esto se hace agregando: /<número-de-bits> a la dirección IP (o nombre de
dominio), y Nmap explorará cada dirección IP para la cual los primeros <numero-de-
bits> son los mismos para la dirección IP de referencia (o IP del nombre de dominio)
dada. Por ejemplo, si el objetivo es: 192.168.10.0/24 se explorarán los 256 equipos
cuyas direcciones IP están entre 192.168.10.0 y 192.168.10.255, inclusive;
192.168.10.49/24 explorará exactamente los mismos objetivos.
Nmap permite especificar una lista de números y rangos, separada por comas, para
cada octeto. Por ejemplo: 192.168.3-5,7.1 explorará las direcciones 192.168.3.1,
192.168.4.1, 192.168.5.1 y 192.168.7.1 .
Ejemplos.
nmap -v -A scanme.nmap.org
nmap -v -sP 192.168.0.0/16 10.0.0.0/8
nmap -v -iR 10000 -PN -p 80
Los argumentos usados en este ejemplo son: -A, para habilitar la detección de sistema
operativo, exploración de script y traceroute; -T4 para una ejecución más rápida; y
también el nombre de dominio.
Nessus
Nessus es un software que sirve para explorar las vulnerabilidades de los equipos
conectados a una red computacional, es decir que busca bugs de seguridad. Consiste
de dos partes, un servidor nessusd, que es quien realiza la exploración de los
objetivos, y un cliente nessus, que muestra el reporte de los resultados de la
exploración. Nessus tiene una versión libre y una versión de paga, ésta última tiene la
ventaja de permitir el acceso a las vulnerabilidades recientemente descubiertas. Para
la detección de dispositivos puede usar algún escáner propio o externo, como nmap.
Nessus maneja las pruebas de vulnerabilidad en forma de plug-ins, mismos que son
obtenidos automáticamente por el servidor nessus del sitio principal de Nessus. Se
puede concluir que Nessus es una herramienta de evaluación de vulnerabilidades.
Instalación de Nessus 5
Para la instalación primero es requerido la descarga del servidor a diferencia de la
práctica de cliente servidor donde utilizamos directamente los programas apt o yum
para acceder a los repositorios publicos de Ubuntu o Federoa en esta ocasión
descargaremos el archivo desde fuera, especificamente desde el sitio oficial de
Nessus (www.nessus.org ) .
Nota: Los siguientes son unos pasos breves, se recomienda ampliamente que se
sigan los tutoriales de instalación y configuración que ofrece el sitio oficial ya que están
completos y abarcan todas las posibles eventualidades que pueden surgir.
1. Ir al sitio de descarga.
2. Seleccionar nessus para el Sistema Operativo que se usara.
3. Descargar el manual de instalación. Liga PDF
4. Descargar el manual de usuario. LIGA PDF
5. Seguir las indicaciones de la guia de instalación (Ubuntu Pag. 13 a 15)
6. Seguir los pasos de arranque el Dameon ( Ubuntu Pag. 15 a 17)
7. Inicie el servidor de Nessus
8. Para los pasos de instalación de licencia y creación de usuarios siga lo indicado
a partir de la página 30 hasta la 41 (Todo los S.O.)
9. Para la instalación de licencia asegúrese de utilizar HomeFeed que es una
versión gratuita.
NOTA: Tanto la guía de instalación y la guía de usuario tienen incluido la
configuración y manejo de Nessus para todo los S.O. en los que da soporte,
por lo tanto deben de navegar a través del índice del inicio.
NOTA 2: Si aparece lo del sitio no seguro se debe que el proceso que sigue
SSL requiere de un tercero para validar pero es algo que no se hará para esta
práctica, configure su navegador para que acepte añadir una excepción a su servidor
Nessus. Mientras se estén siguiendo los pasos de la guía de instalación notará que
deberá registrar Nessus, se utilizará la versión casera que no tiene costo.
Una vez esté inicializado Nessus, siga los pasos del manual de usuario para su
ejecución. Recuerde que el puerto por defecto es 8834. El objetivo será generar una
auditoría a nuestros propios equipos o el de un compañero.
Figura 7 - Reporte de aplicar una auditoría a un dispositivo Android en la red del TEC
Si aparecen reportes de alto riesgo se le recomienda seguir los pasos indicados por
Nessus para eliminar la amenaza. Si por alguna razón aparecen varios malware o
incluso reportes de ser parte de una Botnet se recomienda una limpieza completa y
reinstalar desde cero el S.O.
FAQ:
Error: No se encuentra el archivo Python.h
Solución: Instalar el paquete python-dev (ubuntu) ó python-devel (fedora)
Laboratorio: VULNERABILIDADES
Nmap.
(1) - Para instalar Nmap:
Abre una terminal.
Teclea:
Para Fedora:
yum install nmap
Para Ubuntu:
apt-get install nmap
Nota: esta opción puede no funcionar bien con Ubuntu en máquina virtual. A
veces éstas no funcionan bien con acceso a dispositivos diferentes al disco
duro o USBs, o a puertos
Nessus
(4)- Instale, Nessus, para ello diríjase al sitio oficial para realizar su
descarga e instalación, la versiòn casera es gratuita, es importante que lo
descargue del sitio oficial para tener una versión funcional.
Referencias:
Nmap Reference Guide, http://nmap.org/book/man.html
Anderson, Harry. Introduction to Nessus. Security Focus.
http://www.securityfocus.com/infocus/1741
http://www.nessus.org/nessus/
http://www.nessus.org/documentation/nessus_4.0_installation_guide.pdf
http://indigospv.com/noticias/?p=131
Objetivo
El objetivo de esta sesión es integrar un poco los conocimientos adquiridos en las
prácticas de programación en shell mediante bash y en las de lenguaje C. Con este
laboratorio se espera acercar al alumno a una técnica básica de testing, el
benchmarking.
Introducción
Con la creciente cantidad de tecnología, aplicaciones y sobre todo datos, las
características que debe tener todo código son cada vez más requisitosas. Una de
estas características es el rendimiento (performance) que es la comparación de la
ejecución de un programa con ciertos indicadores de calidad, principalmente tiempo de
ejecución, uso de memoria, uso de disco, cantidad de operaciones de I/O entre otros.
Ejemplo:
Nota: Como se puede ver, sería posible hacer el benchmarking “a mano” simpelmente
ejecutando el comando muchas veces, sin embargo cuando es necesario ejecutar
cientos o miles de instancias del programa variando pequeñas cosas, es bastante más
práctico el uso de scripts.
Para esto, el lenguaje C cuenta con directivas (o pragmas) que son partes del código
que son ejecutadas por el preprocesador al momento de compilar. Estas líneas
comienzan con el caracter #. Los #include 's al incio de nuestros códigos son un
ejemplo de directivas.
Otro ejemplo de directivas con el que trabajaremos en este laboratorio es #define. Esta
directiva crea un macro (una asociación de un identificador con una cadena de
caracteres). Haciendo uso de esta directiva es posible cambiar todas las instancias del
la cadena de caracteres del identificador en el código, por la cadena de caracteres
asignada en el #define al momento de compilación.
Ejemplo:
#define NUM=5
int main(){
for(NUM;NUM>0;NUM--){
//CODIGO
}
}
//En este caso, el CODIGO se ejecutaría 5 veces, ya que, al momento de compilación,
todas las instancias de NUM fueron sustituidas con el número 5.
Ejemplo:
MiPrograma.c
#define NUM
int main(){
for(NUM;NUM>0;NUM--){
//CODIGO
}
}
COMPLEMETO 4
I.Seguridad en informática.
i.Vulnerabilidades, Amenazas, Ataques y Controles.
ii.Errores de programa no mal intencionados.
i.Buffer Overflows
II. Vulnerabilidades en programas: Buffer Overflow
i.Estructura en memoria de un programa:
ii.Buffer overflow.
III. Referencias
IV.Laboratorio
Seguridad en informática.
Cualquier sistema es más vulnerable en su punto más débil. Un ladrón que intente
robar algo de tu casa no intentará penetrar una puerta metálica de cinco
centímetros de ancho si una ventana le da un acceso más fácil. Del mismo modo,
un sistema sofisticado de seguridad física perimetral no compensa el acceso sin
vigilancia a través de una simple línea telefónica y un MODEM. Esta idea es uno de
los principios de la seguridad computacional.
Sin embargo, podemos ver una pequeña fractura en la pared, una vulnerabilidad
que amenaza la seguridad de la persona. Si el agua se eleva al nivel (o más allá) de
la fractura, explotará la vulnerabilidad y dañará a la persona.
¿Cómo podemos abordar estos problemas? Usamos un control como una medida
de protección. Esto es, un control es una acción, un dispositivo, un procedimiento o
una técnica que remueve o reduce una vulnerabilidad. En la Figura 1, la persona
puede poner un dedo en un hoyo y así controlar la amenaza de que el agua se
fugue, hasta que encuentre una solución al problema más permanente. En general,
podemos describir la relación entre amenazas, controles y vulnerabilidades de esta
forma: Una amenaza se bloquea por un control de una vulnerabilidad.
Los daños ocurren cuando una amenaza se lleva a cabo contra una vulnerabilidad.
Para proteger contra el daño, entonces, podemos neutralizar la amenaza, cerrar la
vulnerabilidad, o ambos. La posibilidad de que un daño ocurra se conoce como
riesgo. Podemos hacer frente a los daños de diferentes maneras. Podemos tratar
de:
⇒ prevenir, bloqueando el ataque o cerrando la
vulnerabilidad,
⇒ disuadir, haciendo el ataque más difícil pero no imposible,
⇒ desviar, volviendo otro objetivo más atractivo, o este no
tanto,
⇒ detectar, cuando sucede o algún tiempo después,
⇒ recuperar de sus efectos.
Buffer Overflows
Buffer overflow.
Código auth_overflow.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
strcpy(password_buffer, password);
if(strcmp(password_buffer, "brillig") == 0)
auth_flag = 1;
if(strcmp(password_buffer, "outgrabe") == 0)
auth_flag = 1;
return auth_flag;
}
-=-=-=-=-=-=-=-=-=-=-=-=-=-
Access Granted.
-=-=-=-=-=-=-=-=-=-=-=-=-=-
[jnolazco@localhost pract15]$
[jnolazco@localhost pract15]$ ./auth_overflow outgrabe
-=-=-=-=-=-=-=-=-=-=-=-=-=-
Access Granted.
-=-=-=-=-=-=-=-=-=-=-=-=-=-
[jnolazco@localhost pract15]$ ./auth_overflow test
Access Denied.
[jnolazco@localhost pract15]$
Se establecen dos puntos en los que queremos que la ejecución del programa se
detenga con el comando break.
(gdb) continue
Continuing.
-=-=-=-=-=-=-=-=-=-=-=-=-=-
Access Granted.
-=-=-=-=-=-=-=-=-=-=-=-=-=-
Se puede ver claramente que es posible utilizar esta vulnerabilidad a nuestro favor
por dos fallas en el código. Primero, no se verifica el tamaño del buffer que se va a
copiar permitiendo que el usuario pueda proporcionar un buffer más grande del
que el programador había considerado sobre-escribiendo así los valores de las
siguientes localidades de memoria. El segundo error de código es que una variable
tan sensible está más adelante en memoria que un buffer vulnerable.
Referencias
Erickson, Jon. Hacking: The Art Of Exploitation. 2° Ed.
Stevens, Richard; Rago, Stephen. Advanced Programming in the Unix Environment.
2°Ed.
Pfleeger, Charles. Security in Computing. 4th Ed. Prentice Hall. 2006.
Laboratorio
Laboratorio Dr Nolazco
Laboratorio Dr Icaza
Laboratorio de Sistemas Operativos. Dr. Juan Arturo Nolazco. 2009.
[1]
COMPLEMETO 8 PDF