Mostrando entradas con la etiqueta networking. Mostrar todas las entradas
Mostrando entradas con la etiqueta networking. Mostrar todas las entradas

martes, 15 de marzo de 2016

TCP Keep Alive - how it works

Besides being a short entry this will be the first one in English. Why? mmmm, good question, not sure to be honest. I suppose English will make the entries accessible to more people and that is good reason enough :D to me, I'm not saying all of them will be in English from now on (who knows haha).

So a few days ago I faced an awkward situation that kind of pushed me to see how the TCP keep alive feature works on the Linux Kernel. Nothing life changing, but I wanted to share it here for you out there and for me as well xD, is fun how many times I come back to my old entries looking for commands or answers :D.

The problem was quite simple, on the server side a simple netstat showed more than 20k TCP connections on ESTABLISHED state. However all the clients that had started these connections were shutdown... yes, shutdown, they weren't even online. So, how is this possible? Well, you will understand it after this entry, hopefully XD.

Kernel TCP keep-alive configuration


Our lovely Kernel provides 3 parameters to handle TCP keep-alive behavior:
  • tcp_keepalive_time: number of seconds a connection needs to be idle before keep-alive tests begin. The default value is 7200 (2 h), this parameter is valid ONLY if the option SO_KEEPALIVE is set on the socket.
  • tcp_keepalive_intvl: once the keep-alive tests begin, this value states the number of seconds between each test. The default value is 75 (1min 15 sec).
  • tcp_keepalive_probes: the number of probes that must fail before the connection gets terminated. The default value is 9.
Like every Kernel parameter we can access these 3 guys through the /proc FS, here you have them on one of the test instances:

[root@ip-172-31-24-218 ec2-user]# cat /proc/sys/net/ipv4/tcp_keepalive_intvl
75
[root@ip-172-31-24-218 ec2-user]# cat /proc/sys/net/ipv4/tcp_keepalive_probes
9
[root@ip-172-31-24-218 ec2-user]# cat /proc/sys/net/ipv4/tcp_keepalive_time
7200
[root@ip-172-31-24-218 ec2-user]#


 The test scenario is the following:

The test scenario consists of basically 2 EC2 instances:
  • A, IP 172.31.24.219. This will be the source of the connections.
  • B, IP 172.31.24.218. This will be the so called server instance.
to speed up the tests I reduced, on instance B, tcp_keepalive_time from 7200 to 10, the following way:

[root@ip-172-31-24-218 ec2-user]# echo 10 > /proc/sys/net/ipv4/tcp_keepalive_time
[root@ip-172-31-24-218 ec2-user]# cat /proc/sys/net/ipv4/tcp_keepalive_time
10
[root@ip-172-31-24-218 ec2-user]#


Test 1 "Testing keep-alive"


To simulate the server I used the well known TCP swiss army knife, nc :P (same on the client side nc 172.31.24.218 3333). Basically started nc in background listening on port 3333 on instance B and then started tcpdump to capture the traffic coming to that port from instance A:

[ec2-user@ip-172-31-24-218 ~]$ nc -l 3333 &
[1] 2536
[ec2-user@ip-172-31-24-218 ~]$ sudo tcpdump -nn port 3333
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes
19:27:27.477761 IP 172.31.24.219.42115 > 172.31.24.218.3333: Flags [S], seq 2777265429, win 26883, options [mss 8961,sackOK,TS val 260487 ecr 0,nop,wscale 6], length 0
19:27:27.477983 IP 172.31.24.218.3333 > 172.31.24.219.42115: Flags [S.], seq 2747775912, ack 2777265430, win 26847, options [mss 8961,sackOK,TS val 260850 ecr 260487,nop,wscale 6], length 0
19:27:27.478456 IP 172.31.24.219.42115 > 172.31.24.218.3333: Flags [.], ack 1, win 421, options [nop,nop,TS val 260488 ecr 260850], length 0



^C
3 packets captured
3 packets received by filter
0 packets dropped by kernel

[1]+  Stopped                 nc -l 3333
[ec2-user@ip-172-31-24-218 ~]$ date
Tue Mar 15 19:33:09 UTC 2016
[ec2-user@ip-172-31-24-218 ~]$


mmmm, I've colored with blue the TCP Handshake, and you can see that between the time the connection was established 19:27:27 and the date command 19:33:09 we have more than 5 minutes. Not even one single packet was exchanged between the instances, so why didn't the keep-alive process kick in considering we set it to 20 seconds? Well, perhaps nc is not setting SO_KEEPALIVE on the socket when it opens it? What does strace have to say about it:

[ec2-user@ip-172-31-24-218 ~]$ strace nc -l 3333
execve("/usr/bin/nc", ["nc", "-l", "3333"], [/* 32 vars */]) = 0
brk(0)                                  = 0xff9000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7ff071850000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
...

blah blah blah
...
blah blah blah
...
set_robust_list(0x7ff071847a20, 24)     = 0
rt_sigaction(SIGRTMIN, {0x7ff070d30780, [], SA_RESTORER|SA_SIGINFO, 0x7ff070d39100}, NULL, 8) = 0
rt_sigaction(SIGRT_1, {0x7ff070d30810, [], SA_RESTORER|SA_RESTART|SA_SIGINFO, 0x7ff070d39100}, NULL, 8) = 0
rt_sigprocmask(SIG_UNBLOCK, [RTMIN RT_1], NULL, 8) = 0
getrlimit(RLIMIT_STACK, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
brk(0)                                  = 0xff9000
brk(0x101a000)                          = 0x101a000
brk(0)                                  = 0x101a000
socket(PF_INET, SOCK_STREAM, IPPROTO_TCP) = 3
setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
 

bind(3, {sa_family=AF_INET, sin_port=htons(3333), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
listen(3, 1)                            = 0
accept(3, ^CProcess 2690 detached
 
[1]+  Killed                  nc -l 3333
[ec2-user@ip-172-31-24-218 ~]$


Cool, I've highlighted the lines where the socket is created and you can see SO_KEEPALIVE is not being set as an option. So... what now? Lets get dirty!!!

Test 2 "If you don't like it, change it!"


So, why don't we just force nc to use SO_KEEPALIVE option? Hell yeah!!! I downloaded netcat source from the official website and did the following changes on network.c file:

[ec2-user@ip-172-31-24-218 ~]$ diff netcat-0.7.1/src/network.c netcat-0.7.1_juan/src/network.c
374a375,383
>   sockopt = 1;
>   if (type == SOCK_STREAM){
>      ret = setsockopt(sock,SOL_SOCKET,SO_KEEPALIVE,&sockopt,sizeof(sockopt));
>      if(ret < 0){
>        close(sock);
>        return -2;
>      }
>   }
>
[ec2-user@ip-172-31-24-218 ~]$


as you can see I just added an if statement that will become true if the socket created is SOCK_STREAM (TCP :D), and will set the SO_KEEPALIVE option on the socket. After that I just issued ./configure and make, now strace shows something more interesting:

[ec2-user@ip-172-31-24-218 netcat-0.7.1_juan]$ strace ./src/netcat -l -p 3333
execve("./src/netcat", ["./src/netcat", "-l", "-p", "3333"], [/* 33 vars */]) = 0
brk(0)                                  = 0x125f000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fef806cb000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=21099, ...}) = 0
mmap(NULL, 21099, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fef806c5000
close(3)                                = 0

...
blah blah blah
...
blah blah blah
...
read(3, " # Redwood Chat\npdb             "..., 4096) = 4096
read(3, "      # ContinuStor Monitor Port"..., 4096) = 4096
read(3, "   3107/udp                # Bus"..., 4096) = 4096
read(3, "lfap        3145/tcp            "..., 4096) = 4096
read(3, "\nh2gf-w-2m       3179/udp       "..., 4096) = 4096
read(3, "    3212/tcp                # Su"..., 4096) = 4096
read(3, "eo-fe         3245/tcp          "..., 4096) = 4096
read(3, "80/tcp                # VS Serve"..., 4096) = 4096
read(3, "        # SDT License Manager\nof"..., 4096) = 4096
close(3)                                = 0
munmap(0x7fef806ca000, 4096)            = 0
socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 3
setsockopt(3, SOL_SOCKET, SO_LINGER, {onoff=1, linger=0}, 8) = 0
setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
setsockopt(3, SOL_SOCKET, SO_KEEPALIVE, [1], 4) = 0
 

bind(3, {sa_family=AF_INET, sin_port=htons(3333), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
listen(3, 4)                            = 0
open("/usr/share/locale/locale.alias", O_RDONLY|O_CLOEXEC) = 4
...

blah blah blah
...
select(4, [3], NULL, NULL, NULL^CProcess 6656 detached
 
[ec2-user@ip-172-31-24-218 netcat-0.7.1_juan]$


so now we do have SO_KEEPALIVE in place, lets see if it works:

[ec2-user@ip-172-31-24-218 netcat-0.7.1_juan]$ ./src/netcat -l -p 3333 &
[1] 6657
[ec2-user@ip-172-31-24-218 netcat-0.7.1_juan]$ sudo tcpdump -nn port 3333
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes
20:23:48.747981 IP 172.31.24.219.42122 > 172.31.24.218.3333: Flags [S], seq 723623108, win 26883, options [mss 8961,sackOK,TS val 1105802 ecr 0,nop,wscale 6], length 0
20:23:48.748190 IP 172.31.24.218.3333 > 172.31.24.219.42122: Flags [S.], seq 4243448461, ack 723623109, win 26847, options [mss 8961,sackOK,TS val 1106167 ecr 1105802,nop,wscale 6], length 0
20:23:48.748679 IP 172.31.24.219.42122 > 172.31.24.218.3333: Flags [.], ack 1, win 421, options [nop,nop,TS val 1105803 ecr 1106167], length 0


20:23:58.764759 IP 172.31.24.218.3333 > 172.31.24.219.42122: Flags [.], ack 1, win 420, options [nop,nop,TS val 1108672 ecr 1105803], length 0
20:23:58.765399 IP 172.31.24.219.42122 > 172.31.24.218.3333: Flags [.], ack 1, win 421, options [nop,nop,TS val 1108307 ecr 1106167], length 0


20:25:13.773009 IP 172.31.24.218.3333 > 172.31.24.219.42122: Flags [.], ack 1, win 420, options [nop,nop,TS val 1127424 ecr 1108307], length 0
20:25:13.773926 IP 172.31.24.219.42122 > 172.31.24.218.3333: Flags [.], ack 1, win 421, options [nop,nop,TS val 1127059 ecr 1106167], length 0


20:26:29.036973 IP 172.31.24.218.3333 > 172.31.24.219.42122: Flags [.], ack 1, win 420, options [nop,nop,TS val 1146240 ecr 1127059], length 0
20:26:29.037684 IP 172.31.24.219.42122 > 172.31.24.218.3333: Flags [.], ack 1, win 421, options [nop,nop,TS val 1145876 ecr 1106167], length 0


so our fancy workaround actually worked!! we can see how after 10 seconds of inactivity  the keep-alive probes kick in and they are sent every 75 seconds. The probes are basically ACK packets with no real content, here we can see how the probes are answered by instance A with a plain ACK as well.

Test 3 "Let the show begin"


Ok, now we have SO_KEEPALIVE enabled, therefore we should be able to test the all the parameters. Again, to speed up the process we'll reduce some of them again to the following values:

[root@ip-172-31-24-218 netcat-0.7.1]# echo 5 > /proc/sys/net/ipv4/tcp_keepalive_probes
[root@ip-172-31-24-218 netcat-0.7.1]# echo 20 > /proc/sys/net/ipv4/tcp_keepalive_intvl
[root@ip-172-31-24-218 netcat-0.7.1]# cat /proc/sys/net/ipv4/tcp_keepalive_probes
5
[root@ip-172-31-24-218 netcat-0.7.1]# cat /proc/sys/net/ipv4/tcp_keepalive_intvl
20
[root@ip-172-31-24-218 netcat-0.7.1]#


now the probes should be sent every 20 seconds and if 5 of them fail, the connection should be terminated. Ok, but if I run the same test again, the probes won't really fail because the connection is perfectly fine, so I need to cause a problem here.

The easiest way to make sure the connection will be idle and that instance A won't answer the keep-alive probes is by dropping all the packets on instance A right after the connection is ready. I did this by adding the following iptables rules right after the connection was established:

iptables -A OUTPUT --dst 172.31.24.218 -j DROP
iptables -A INPUT --src 172.31.24.218 -j DROP


Note: I dropped everything going out to instance B and everything coming in from instance B.

so there we go...

[ec2-user@ip-172-31-24-218 netcat-0.7.1]$ ./src/netcat -l -p 3333 &
[2] 27020
[ec2-user@ip-172-31-24-218 netcat-0.7.1]$ sudo tcpdump -nn port 3333
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes
21:57:41.857514 IP 172.31.24.219.42126 > 172.31.24.218.3333: Flags [S], seq 2971447882, win 26883, options [mss 8961,sackOK,TS val 2514081 ecr 0,nop,wscale 6], length 0
21:57:41.857818 IP 172.31.24.218.3333 > 172.31.24.219.42126: Flags [S.], seq 1633813684, ack 2971447883, win 26847, options [mss 8961,sackOK,TS val 2514445 ecr 2514081,nop,wscale 6], length 0
21:57:41.858211 IP 172.31.24.219.42126 > 172.31.24.218.3333: Flags [.], ack 1, win 421, options [nop,nop,TS val 2514082 ecr 2514445], length 0



21:57:51.884756 IP 172.31.24.218.3333 > 172.31.24.219.42126: Flags [.], ack 1, win 420, options [nop,nop,TS val 2516952 ecr 2514082], length 0
21:58:11.948771 IP 172.31.24.218.3333 > 172.31.24.219.42126: Flags [.], ack 1, win 420, options [nop,nop,TS val 2521968 ecr 2514082], length 0
21:58:31.980765 IP 172.31.24.218.3333 > 172.31.24.219.42126: Flags [.], ack 1, win 420, options [nop,nop,TS val 2526976 ecr 2514082], length 0
21:58:52.012770 IP 172.31.24.218.3333 > 172.31.24.219.42126: Flags [.], ack 1, win 420, options [nop,nop,TS val 2531984 ecr 2514082], length 0
21:59:12.044761 IP 172.31.24.218.3333 > 172.31.24.219.42126: Flags [.], ack 1, win 420, options [nop,nop,TS val 2536992 ecr 2514082], length 0
21:59:32.076768 IP 172.31.24.218.3333 > 172.31.24.219.42126: Flags [R.], seq 1, ack 1, win 420, options [nop,nop,TS val 2542000 ecr 2514082], length 0

^C
9 packets captured
9 packets received by filter
0 packets dropped by kernel

[2]+  Stopped                 ./src/netcat -l -p 3333
[ec2-user@ip-172-31-24-218 netcat-0.7.1]$


interesting, right? keep-alive probes kicked in after 10 seconds as expected, then we have a probe every 20 seconds (no answer from instance A), and after the 5th probe we can see a RST+ACK package going from B to A trying to terminate the connection. This is exactly the behavior we were expecting :D.

So coming back to 20k+ ESTABLISHED connections situation, now we can understand why a situation like that is possible. Basically all the clients, for some awkward and really hard to reproduce reason were crashing and not finishing the connections properly, so on the server side due to the fact that the sockets used for the connections weren't using SO_KEEPALIVE option, the connections remained ESTABLISHED for the eternity.

Could this be a big problem? well it might be:

  • The first thing that comes to my mind is wasted memory. These established connections even though they are not being actively used, they need kernel memory to exist, so do the math :D. 
  • If a new connection comes in to the server and the source IP and source port match with one of the connections in ESTABLISHED state that connection will probably fail.
  • If you have a limit number of sockets available on the server I don't think you can afford having them allocated to this Ghost connections.

So why not having TCP keep-alive enabled by default? Good question... I guess that when we are talking about too many connections, handling the keep-alive could put some overhead on the kernel side. Imagine that for every connection the kernel should have to keep track of the time the last packet came in and then set timers to trigger the keep-alive probes.

I hope I was clear enough, otherwise you can always leave a comment :P.

... it wasn't a short entry after all.

lunes, 8 de junio de 2015

UDP en GNU/Linux Parte II

Esta entrada es la continuación de UDP en GNU/Linux Parte I y la idea es continuar con las pruebas para entender un poco mas cómo funciona el stack UDP en GNU/Linux.

En la publicación anterior quedó pendiente probar packet receive errors  en una condición de buffer insuficiente. Dijimos que esta condición se puede dar en un escenario donde los paquetes arriban al host a una tasa mayor que con la que son retirados del buffer del socket. Para comprobar esto vamos a precisar algunos números y un receptor UDP lento.

Receptor UDP lento

La verdad es que no se me ocurrió ninguna idea maravillosa para hacer un receptor lento de paquetes UDP, así que hurgando un poco en internet armé un pequeño cliente.c (si ¬¬ .c, ¿qué tiene de malo?). El cliente básicamente realiza las siguientes tareas:
  • Abre un socket UDP y escucha en el puerto 5555.
  • Imprime en pantalla el tamaño del buffer del socket.
  • Luego en un loop toma del buffer del socket 1 paquete por segundo e imprime en pantalla un contador de paquetes leidos.
Acá pueden ver una ejecución abortada de ejemplo:
juan@ubuntu:~$ ./cliente
--Socket creado!
-Configuracion del socket tamanio del buffer de recepcion: 163840
^C
juan@ubuntu:~$ 


Como ven el tamaño del buffer por defecto es de 163840 bytes (160Kbytes). Este valor no es aleatorio ni nada que se le parezca, el tamaño está definido por la variable de kernel net.core.rmem_default

root@ubuntu:/home/juan# sysctl -a|grep rmem_default
net.core.rmem_default = 163840
root@ubuntu:/home/juan#


por ejemplo, se podría ampliar de la siguiente manera

root@ubuntu:/home/juan# sysctl -w net.core.rmem_default=1638400
net.core.rmem_default = 1638400
root@ubuntu:/home/juan# sysctl -a|grep rmem_default
net.core.rmem_default = 1638400
root@ubuntu:/home/juan# 


y se vería reflejado en la próxima ejecución de nuestro cliente

root@ubuntu:/home/juan# ./cliente
--Socket creado!
-Configuracion del socket tamanio del buffer de recepcion: 1638400
^C
root@ubuntu:/home/juan#  


Entonces, resumiendo los números:
  • Tamaño del buffer de recepción 163840 bytes.
  • Enviaremos paquetes de 1024 bytes forjados con Hping3. Por lo tanto, 160 paquetes son suficientes para llenar el buffer.
  • Se enviarán 2000 paquetes a una tasa de 10 paquetes por segundo.
  • La aplicación consume 1 paquete cada 1 segundo.
Está claro que si esta situación se prolonga lo suficiente en el tiempo, en algún momento el buffer se llenará y el sistema deberá descartar paquetes. Si bien es una condición un poco exagerada a fines prácticos resulta útil.

Antes de lanzar nuestro cliente lento tomamos los valores de las estadísticas del sistema:

root@ubuntu:/home/juan# netstat -su
Udp:
    20 packets received
    0 packets to unknown port received.
    0 packet receive errors
    22 packets sent
...
root@ubuntu:/home/juan#


lanzamos el cliente

root@ubuntu:/home/juan# ./cliente
--Socket creado!
-Configuracion del socket tamanio del buffer de recepcion: 163840


y apuntamos el cañón hping3 al host:

root@moon:/home/juan/pruebas# hping3 192.168.0.10 -2 -c 2000 --fast -E 1024 -d 1024 -p 5555 -V
using wlan0, addr: 192.168.0.2, MTU: 1500
HPING 192.168.0.10 (wlan0 192.168.0.10): udp mode set, 28 headers + 1024 data bytes
[main] memlockall(): Success
Warning: can't disable memory paging!

--- 192.168.0.10 hping statistic ---
2000 packets transmitted, 0 packets received, 100% packet loss
round-trip min/avg/max = 0.0/0.0/0.0 ms
root@moon:/home/juan/pruebas#


esta vez hay nuevos parámetros en hping3:
  • --fast: le indica que debe mandar 10 paquetes por segundo.
  • --E 1024: indica que debe meter como payload de los paquetes el contenido del archivo llamado 1024 (un archivo con 1024 bytes en 0).
  • -d 1024: este parámetro es el que realmente indica cuántos bytes del archivo indicado por -E se van a mandar como payload. En esta caso se arman datagramas de 1024 bytes.
  • -c 2000: indica que se enviarán 2000 paquetes.
en definitiva se enviaron 2000 paquetes con 1024 bytes como payload cada uno de ellos (en total se enviaron unos 2048000 bytes, claramente superamos el tamaño del buffer).

Vemos la salida de la aplicación (dado que la función de lectura es bloqueante, la aplicación no termina por sus propios medios después de leer el último paquete, luego de unos segundos sin nuevos paquetes la aborté con Ctrl+C)

root@ubuntu:/home/juan# ./cliente
--Socket creado!
-Configuracion del socket tamanio del buffer de recepcion: 163840
Paquete numero 0
Paquete numero 1
Paquete numero 2
Paquete numero 3
Paquete numero 4
Paquete numero 5
Paquete numero 6
Paquete numero 7
...

Paquete numero 279
Paquete numero 280
Paquete numero 281
Paquete numero 282

^C
root@ubuntu:/home/juan#


Entonces se enviaron 2000 paquetes pero nuestra aplicación lenta solo pudo leer 283 de ellos!!! A donde fueron los restantes mmmm 1717 paquetes?

root@ubuntu:/home/juan# netstat -su
Udp:
    303 packets received
    0 packets to unknown port received.
    1717 packet receive errors
    22 packets sent
...
root@ubuntu:/home/juan#


Claramente fueron al cielo de los paquetes. Fueron completamente descartados por el kernel debido a que no había espacio donde almacenarlos.

Dado que leemos 1 paquete por segundo y agregamos 10 por segundo, podríamos decir que llegan un neto de 9 paquetes por segundo al buffer. A 9 paquetes por segundo el buffer se llena en unos 17 segundos aproximadamente. Es decir que, si la matemática no me falla, a partir de los 17 segundos estaríamos perdiendo al menos 9 paquetes por segundo.

Hay dos maneras de enfrentar esta situación, dado que UDP no tiene control de flujo e implementarlo en la capa de aplicación sería un delirio, podemos:
  • Incrementar la eficiencia del consumidor, leyendo mas paquetes por segundo.
  • Aumentando el tamaño del buffer de recepción para poder soportar el asedio de paquetes sin perderlos.
Entonces supongamos que mejoramos el código y multiplicamos por 7 la eficiencia de la aplicación y ahora es capaz de leer 7 paquetes por segundo. Matemáticamente estamos en la misma (en el horno), estarían ingresando un neto de 3 paquetes por segundo, ahora el buffer se llenaría en 53 segundos y volvemos a tener pérdida de paquetes. El paso siguiente es aumentar el tamaño del buffer. Sabemos que vamos a recibir una ráfaga de 2000 paquetes de 1024 bytes, osea 2Mbytes es decir que podríamos configurar un buffer de 2MBytes y quedarnos re tranquilos, pero sería algo extremo.

Con el nuevo código llenamos el buffer en 53 segundos, osea que aún nos quedan 147 segundos con pérdida de paquetes. Pero ahora vamos a ampliar el buffer multiplicándolo por 4 (NOTA: vale aclarar que este nuevo cambio se aplicará a todos los nuevos sockets creados).

root@ubuntu:/home/juan# echo "163840*4"|bc
655360
root@ubuntu:/home/juan# sysctl -w net.core.rmem_default=655360
net.core.rmem_default = 655360

root@ubuntu:/home/juan# sysctl -w net.core.rmem_max=655360
net.core.rmem_max = 655360

root@ubuntu:/home/juan# sysctl -a | grep net.core.rmem_default
net.core.rmem_default = 655360
root@ubuntu:/home/juan# 


En teoría, con este nuevo buffer de solo 4 veces mayor tamaño no deberíamos ver pérdida de paquetes.

root@ubuntu:/home/juan# netstat -su 
Udp:
    2072 packets received
    0 packets to unknown port received.
    0 packet receive errors
    21 packets sent
root@ubuntu:/home/juan#


Ejecución cliente

root@ubuntu:/home/juan# ./cliente
--Socket creado!
-Configuracion del socket tamanio del buffer de recepcion: 655360
Paquete numero 7
Paquete numero 14
Paquete numero 21
Paquete numero 28
Paquete numero 35
Paquete numero 42
Paquete numero 49
Paquete numero 56

...
Paquete numero 1960
Paquete numero 1967
Paquete numero 1974
Paquete numero 1981
Paquete numero 1988
Paquete numero 1995
2000 paquetes leidos!!!

root@ubuntu:/home/juan#

Y corroboramos con las estadísticas:

root@ubuntu:/home/juan# netstat -su
Udp:
    4072 packets received
    0 packets to unknown port received.
    0 packet receive errors
    21 packets sent

root@ubuntu:/home/juan#

El aumento del tamaño del buffer y la mejora en la aplicación permitieron que esta vez no haya pérdida de paquetes.

sábado, 3 de abril de 2010

Pequeeeeña intro MDNS (Multicast DNS):

Hoy (01/04/2010), mientras preparaba la próxima entrada del blog "casualmente" tenía wireshark corriendo y me encontré con un paquete que me llamó poderosamente la atención. El protocolo es MDNS, lo había visto antes pero siempre creí que se trataba de alguna de las aplicaciones que estaba corriendo.

Intro:

Googleando un poco encontré de que se trata: "Multicast DNS is a way of using familiar DNS programming interfaces, packet formats and operating semantics, in a small network where no conventional DNS server has been installed." (http://www.multicastdns.org/). Es decir, se trata de una implementación del protocolo de resolución de nombres (DNS) para redes de área local, donde no existe un servidor DNS real. Producto de los grupos Zero Configuration Networking y DNS Extensions, creado con el objetivo de facilitar la configuración de redes locales que no cuentan con un servidor de nombres propio.

Según el draft (http://files.multicastdns.org/draft-cheshire-dnsext-multicastdns.txt) "Clients performing DNS-like queries for DNS-like resource records by sending DNS-like UDP query and response packets over IP Multicast to UDP port 5353." se trata de clientes realizando consultas tipo DNS por registros tipo DNS enviando paquetes (tipo DNS) al puerto 5353 UDP  sobre direcciones IP Multicast (JA!, para los que creen que las clase D no se usan jajaaj).

Las direcciones Multicast o clase D van de 224.0.0.0 a 239.255.255.255, y se caracterizan por: una dirección Multicast está asociada con un grupo de receptores interesados, cuando uno de los interesados envía un paquete a una dirección Multicast todos los demás lo reciben, los routers que se encuentren entre estos interesados deben ser capaces de reenviar estos paquetes a cada uno de ellos.

Al tratarse de una dirección Multicast, todos los asociados reciben todos los paquetes, y de esta manera las caches de nombres se mantienen actualizadas. Cada respuesta a una consulta Multicast DNS trae un campo donde se especifica el tiempo de validez que tiene la respuesta, luego de que expire ese tiempo es necesario volver a efectuar la consulta.

Este servicio de DNS cuenta también con un TLD (Top-Level Domain) propio y es ".local.". Todo FQDN (Fully Qualified Domain Name, nombre completo xD) que finalice con ".local." se lo considera de enlace local, es decir... es un análogo a las direcciones IP privadas, que sólo valen en el ámbito local de la red y de nada sirven fuera de ella. Toda consulta DNS que finalice con ".local." debe ser enviada a la dirección 224.0.0.251 (si se trata de IPv4, o FF02::FB si se trata de IPv6).

Existen 3 tipo de consultas: las consultas convencionales 1-consulta->1-respuesta, 1-consulta->N-respuestas y el ultimo tipo que consiste en consultas continuas.

En fin... eso es mas o menos a modo de introducción :D. Si realmente querés saber mas, está el link del borrador mas arriba, de ahora en mas voy a intentar controlar/activar/desactivar este servicio de mi laptop. Resulta ser que en la mayoría de las distribuciones de GNU/Linux viene instalado por defecto un servicio que implementa este protocolo, y se llama Avahi (http://avahi.org/). Avahi es capaz de publicar y descubrir dispositivos, y servicios disponibles en la red local, sin necesidad de realizar configuración alguna.

Veamos si el servicio está corriendo :D  (hagamos de cuenta que nunca vi el paquete con wireshark xD):
"Lo sospeché desde un principio..." asi es, tengo corriendo como demonio a avahi-daemon



Buscando un poco mas se puede ver que escucha en el puerto UDP 5353 de todas las interfaces disponibles, a la espera de consultas/respuestas que pueden provenir de cualquier dirección de red.



Ahora probemos la facilidad de uso de este protocolo. La lan de prueba está compuesta por 2 hosts, la laptop con direccion 192.168.206.1 (nombre moon) y una vm con dirección 192.168.206.134 (nombre ubuntu). Ambos hosts tienen instalado Ubuntu 9.10, que trae por defecto instalado avahi-daemon versión 0.6.25. Ninguno de los host recibió algún tipo de configuración de este servicio, ambos corren como fueron instalados. En el host moon estará nuestro amigo wireshark capturando los paquetes para ver lo que sucede :D.

Buscando a "ubuntu" desde "moon":

Sabemos que está conectadp a la red un host de nombre "ubuntu", pero no sabemos la dirección IP que le fue asignada, y no contamos con un servidor DNS local :D, maldita sea!, pero para eso está corriendo avahi-daemon. Para conocer la dirección de ese host nos basta con hacer un "ping ubuntu.local.", como muestra la imagen



ahora veamos lo que capturó nuestro amigo :D.
Wireshark nos muestra 4 paquetes MDNS, los dos primeros son la resolución directa del nombre de host "ubuntu.local.".
-192.168.206.1 (moon) envía una consulta MDNS a 224.0.0.251 (Multicast IP Address) preguntando por la dirección de "ubuntu.local.".
-192.168.206.134 (ubuntu) envía una respuesta MDNS a 224.0.0.251 (Multicast IP Address) diciendo que 192.168.206.134 corresponde al "host ubuntu.local.".
Los últimos dos paquetes son la resolución inversa, es decir, intenta averiguar el nombre que le corresponde a la dirección 192.168.206.134, esto debe ser una suerte de mecanismo de seguridad, para ver que sean consistentes:
-192.168.206.1 (moon) envía una consulta inversa a la dirección 224.0.0.251, consultando por el nombre que le corresponde a "134.206.168.192.in-addr.arpa.".
-192.168.206.134 (ubuntu) rsponde a la dirección 224.0.0.251, diciendo que esa dirección le corresponde al nombre "ubuntu.local.".



Exáctamente lo mismo podemos observar si ahora realizamos el ping desde ubuntu hacia moon.local.



Publicando y Descubriendo servicios:

Junto con MDNS se puede utilizar algo llamado DNS-SD (DNS-Service Discovery), que consiste en dar a conocer y encontrar servicios locales utilizando MDNS. De nuevo se trata de algo de simple puesta en marcha. Como ejemplo publicaremos dos servicios en el host de nombre moon (FQDN moon.local.), un servidor web en el puerto 80 y un ftp en el puerto 21. El proceso de publicación de los servicios consiste en crear un archivo con sintaxis XML en el directorio /etc/avahi/services/, con la condición que el nombre del archivo debe finalizar en ".service" (sin las comillas :D), como el siguiente:



Para entender bien la sintaxis basta con dar una breve leída a "man avahi.service" :D. Luego de configurar adecuadamente los servicios que querramos reiniciamos el demonio avahi con "avahi-daemon -r" y listo, toda la magia de la publicación de servicios es esa jajaja. Ahora hay que comprobar que los servicios están siendo publicados, para eso instalé dos porgramas mdns-scan y mzclient, NO instalen mdns-scan es DEMASIADO BASICO (ni siquiera recibe parámetros y solo busca types "_workstation"). Primero veamos los hosts que se encuentran utilizando MDNS ejecutamos mzclient como en la próxima imagen (-r se encarga de resolver los nombres de los hosts):



moon tiene 3 direcciones MAC diferentes porque se trata de las 3 interfaces de red, dos virtuales y la eth0 real. Entonces los hosts siguen bien configurados, ahora busquemos los servicios publicados :D. Para eso usamos la opcion -t, que indica la etiqueta type del archivo de servicios, buscamos primero si hay servicios tipo "_http._tcp", incluimos -r para que resuelva nombres:



Ahora busquemos el ftp :D:



Ambos servicios fueron asociados al nombre "moon.local" con dirección 192.168.206.1, como lo configuramos y como debe ser. Eso es gran parte de la publicación de servicios, para mas información http://avahi.org :P.

Deteniendo el demonioooooooo:

Si nos iteresa detener el demonio solo por un momento o reiniciarlo, lo podemos hacer directamente mediante el script ubicado en /etc/init.d. Ejecutamos sudo /etc/init.d/avahi-daemon stop, o mas elegantemente sudo service avahi-daemon stop, para detenerlo, cambiamos stop por restart, para reiniciarlo y start para iniciarlo cuando está detenido.



Deshabilitando temporalmente al demonio:
Si queremos deshabilitarlo temporalmente pero no borrarlo del sistema podemos recurrir a un pequeño truquillo :D. La idea básicamente consiste en que sencillamente el demonio nunca se inicie xD, y esto lo podemos lograr alterando el script de inicio del demonio, ubicado en "/etc/init/avahi-daemon.conf". Como se ve en la imagen hay que comentar la linea de "start".



En definitiva, se trata de un servicio bastante útil, pero que sinceramente no necesito tener activo en este momento. Posiblemente en una LAN donde todo el tiempo entran y salen dispositivos diferentes sea de suma utilidad, pero no lo veo tan así en una LAN chica con poco cambio de los dispositivos.

sábado, 25 de abril de 2009

Redes IP y Subnetting

Para poder escribir sobre subnettig tengo 2 opciones: partir del supuesto que todos sabemos qué es una red IP o partir explicando qué es una red IP. Como ya terminé los parciales en la facultad (es decir, me sobra un poco de tiempo), voy a empezar por el camino mas largo xD.

Haciendo un mini repaso, sabemos que las direcciones IP están formadas por 32 bits, divididos en 4 octetos que se suelen escribir en decimal para hacer nuestra vida un poco más hermosa xD. Entonces la dirección 11000000101010000000000000000001 la expresamos como 192.168.0.1. En la figura se muestra parte del proceso.


En la siguiente imagen se ven 7 Hosts interconectados, divididos en 3 redes conectadas mediante un router. Todos los Host de una red comparten cierta parte de su dirección IP, precisamente esta parte que comparten se llama dirección de red. En el ejemplo, se ve que los Hosts de la RED 0 comparten los primeros 24 bits de sus direcciones IP, es decir, tienen como dirección de red 190.68.0.0/24. El /24 nos indica que la dirección de red está formada por los primeros 24 bist de la dirección IP, a este número se lo llama máscara de red, y suele representarse en el mismo formato que las direcciones IP, en este caso sería 255.255.255.0.


Con estos nuevos datos ya podemos saber la dirección de red de un interfase si conocemos su máscara de red, el proceso es simple. Hay que realizar la operación AND lógico entre los 32 bits de la dirección IP y los 32 bits de la máscara de red.


Resumiendo un poco todo lo dicho, ahora sabemos que una dirección IP está formada por dos partes, la dirección de red (indicada por la máscara de red) y la del Host.

Antes de olvidarme voy a hacer una aclaración; las direcciones IP se asignan a una interfaz (una interfaz es la frontera entre el Host y el enlace físico, simplificando… la placa de red) de un Host, no al Host en si mismo. ¿Qué quiero decir?, que un mismo Host puede tener mas de una interfaz (como ser el caso de los routers), aunque no sea muy común, y por eso se considera siempre el caso donde los Hosts sólo tienen una interfaz que los une a la red.

En un principio el esquema de direccionamiento IP se había organizado en 5 clases (A, B, C, D y E). Las redes de clase A cuentan con 7 bits para identificar redes y 24 bits para indicar Hosts. Las de clase B 14 bits para redes y 16 para Hosts. Las redes clase C emplean 21 bits para redes y 8 bits para Hosts. Las redes de clase D tienen un propósito especial, y las de clase E son para investigación. En el siguiente cuadro queda bien aclarado este tema de la división entre red y Host.

Este direccionamiento por clases fue pronto dejado de lado, debido a que no era muy elástico y malgastaba grandes cantidades de direcciones. Por ejemplo, una organización que tuviera 500 hosts que direccionar no podría usar una clase C, porque esta clase direcciona solamente 2^8 – 2 hosts (hay 2 direcciones que son de uso especial, las trato mas adelante), osea 254, entonces debería usar una clase B, en este caso contaría con 2^16 - 2 direcciones para sus hosts… pero esto es 65534 direcciones, de las cuales sólo usará 500… es decir malgastaría 65034 direcciones. Por este motivo se malgastaban muchas direcciones y pronto fue necesario re pensar el sistema de direccionamiento IP.

Así fue que surgió un nuevo estándar propuesto por la IETF llamado CIDR (Classless Interdomain Routing), enrutado interdominio sin clases. Con este nuevo estándar ahora las clases fueron dejadas un poco de lado, permitiendo que la longitud de la parte de red de una dirección IP pueda tener cualquier tamaño. Con CIDR nuestra organización del ejemplo ya puede obtener un bloque de direcciones más acotado (y por ende mas barato xD) con 9 bits para hosts tendríamos 2^9 – 2 = 510 direcciones disponibles, lo que es mucho mas preciso. Entonces las direcciones disponibles para la organización serán de la forma a.b.c.d/23 (donde 23 indica la máscara de red).

Otra nota importante! Existe un conjunto de direcciones IPs, llamadas direcciones privadas, que no pertenecen al grupo de direcciones asignadas en Internet o públicas. Estas se separaron para crear las redes locales y de esta forma disminuir el uso de direcciones públicas. Para que una red privada tenga acceso a Internet debe contar con una puerta de enlace que realice NAT o un servidor Proxy que contará con una dirección pública que le dará acceso a Internet a esa red privada. Aca dejo un link donde se listan las direcciones IP privadas y un poco mas de información: http://es.wikipedia.org/wiki/Direcci%C3%B3n_IP

Subnetting:

Es en este punto en el que nace el concepto de Subnetting. Subnetting es el proceso por el cual se toman bits correspondientes a hosts en la dirección IP para armar subredes. De esta forma podemos obtener a partir de una dirección de red varias subredes de menor tamaño. A partir de este momento nuestras direcciones IP tendrán 3 partes, un número de red, un número de subred y un número de Host dentro de la subred.

Veamos un ejemplo para aclarar el tema, supongamos que tenemos una dirección de red como la siguiente 192.168.2.0. Para los perdidos (o los que no leyeron el link que puse antes), esta es una dirección IP privada (NO PUEDE EXISTIR EN INTERNET), y por su formato podemos decir que es de clase C, entonces su máscara de red debe ser /24 (255.255.255.0). Sin aplicar Subnetting estamos frente a una red clase C que nos permite direccionar 2^8 – 2 hosts (interfaces en realidad, recuerden).

Voy a aprovechar para explicar el porqué del “- 2”, dentro de una red (también en las subredes) existen 2 direcciones que no se pueden asignar a una interfaz (o Host) porque son de propósito específico. Estas direcciones son la primera y la última, y se llaman dirección de red y dirección de broadcast respectivamente. La primera indica la red o subred y la última se utiliza para enviar paquetes a todos los miembros de la red o subred. En el caso de nuestro ejemplo la dirección de red es 192.168.2.0 (si!! El resultado de aplicar la máscara con el AND lógico xD) y la dirección de broadcast consiste en poner en 1 todos los bits que se usan para determinar el Host de la red o subred, en nuestro caso es el 4 octeto, entonces nos queda 192.168.2.255 como dirección de broadcast. E ahí la explicación de porque quitar dos direcciones del total, porque esas NO se pueden asignar a interfaz alguna.

Continuando con el ejemplo, ahora supongamos que tenemos una sola red con 100 computadoras repartidas en la organización. Por cierto motivo (privacidad, seguridad, desempeño, tiempo de sobra, etc.) se toma la decisión de dividir esa gran red en 2 redes más pequeñas con 50 computadoras cada una. Las llamaremos Subred Ventas, Subred RRHH. Ahora empezamos con los cálculos: dijimos que queremos armar 2 redes, entonces podríamos tomar 2 bits de los hosts para así poder armar 2^2 - 2 subredes (descartamos la primera y la última subred), es decir 2! Que precisión xD, sólo vamos a desperdiciar una (esto no estaba planeado jaja).

La siguiente imagen muestra el proceso:


De esa forma conformamos las dos redes, donde cada red tiene un espacio de direcciones para 62 interfaces.

El Subnetting se puede hacer sobre cualquier clase de redes, es decir sobre una red Clase A, B, etc. La esencia es que siempre que hagamos Subnetting vamos a obtener varias redes de menor cantidad de hosts (se disminuyen en potencia de 2 por cada bit que tomamos del campo de Host de la dirección IP). La subred entonces queda determinada por esta nueva Máscara de subred.

Como verán en una red de clase B que tenemos 14 bits que definen la red y 16 bits que definen el Host se pueden obtener buenas combinaciones para muchas subredes con muchos hosts, por ejemplo, tomando 6 bits para subredes obtendríamos 62 subredes con 1022 direcciones asignables (jeej me crees?? Probalo).

Cualquier duda, sugerencia, o consulta con respecto al tema, está MAS que bien recibida. Espero que haya expresa bien las ideas ajaja, un saludo. JJ.