¿Dónde puedo conseguir esos archivos de cabecera?
Si no los tienes ya en tu sistema, probablemente no los necesites. Consulta el manual de tu plataforma. Si estás construyendo para Windows, sólo necesitas #include <winsock.h>
.
¿Qué hago cuando bind()
informa “Address already in use”?
Tienes que usar setsockopt()
con la macro SO_REUSEADDR
en el socket de escucha. Consulta la section on bind()
y la section on select()
para ver un ejemplo.
¿Cómo puedo obtener una lista de los sockets abiertos en el sistema?
Usa el comando netstat
. Revisa la página man
para más detalles, pero deberías obtener una buena salida simplemente tecleando:
$ netstat
El único truco es determinar qué socket está asociado a qué programa. :-)
¿Cómo puedo ver la tabla de enrutamiento?
Ejecute el comando route
(en /sbin
en la mayoría de Linuxes) o el comando netstat -r
. O el comando ip route
.
¿Cómo puedo ejecutar los programas cliente y servidor si sólo tengo un ordenador? ¿No necesito una red para escribir programas de red?.
Afortunadamente para ti, prácticamente todas las máquinas implementan un “dispositivo” de red loopback que se sitúa en el kernel y simula ser una tarjeta de red. (Es la interfaz que aparece como «lo
» en la tabla de enrutamiento).
Imagina que estás conectado a una máquina llamada “goat
”. Ejecuta el cliente en una ventana y el servidor en otra. O inicia el servidor en segundo plano (“server &
”“) y ejecuta el cliente en la misma ventana. El resultado del dispositivo loopback es que puedes ejecutar client goat
o ] utilidad «ping»? ¿Qué es ¿ICMP? ¿Dónde puedo encontrar más información sobre raw sockets y SOCK_RAW
?**
Todas tus preguntas sobre raw sockets serán respondidas en W. Richard Stevens’ UNIX Network Programming books. También, busque en el subdirectorio ping/
en el código fuente de Programación de Redes UNIX de Stevens, disponible en línea44.
¿Cómo puedo cambiar o acortar el tiempo de espera de una llamada a connect()
?
En lugar de darte exactamente la misma respuesta que te daría W. Richard Stevens, te remitiré a lib/connect_nonb.c
en el código fuente de UNIX Network Programming45.
La esencia es que haces un descriptor de socket con socket()
, lo configuras como no-bloqueo, llamas a connect()
, y si todo va bien connect()
devolverá -1
inmediatamente y errno
se configurará como EINPROGRESS
. Entonces llama a select()
con el tiempo de espera que quieras, pasando el descriptor del socket tanto en lectura como en escritura. Si no se agota el tiempo de espera, significa que la llamada a connect()
se ha completado. En este punto, tendrás que usar getsockopt()
con la opción SO_ERROR
para obtener el valor de retorno de la llamada a connect()
, que debería ser cero si no hubo error.
Finalmente, probablemente querrá volver a poner el socket en bloqueo antes de empezar a transferir datos sobre él.
Tenga en cuenta que esto tiene el beneficio añadido de permitir a su programa hacer algo más mientras se está conectando. Podrías, por ejemplo, establecer el tiempo de espera a algo bajo, como 500 ms, y actualizar un indicador en pantalla cada vez que se agote el tiempo de espera, luego llamar a select()
de nuevo. Cuando hayas llamado a select()
y se haya agotado el tiempo de espera, digamos, 20 veces, sabrás que es hora de abandonar la conexión.
Como he dicho, echa un vistazo a la fuente de Stevens para un ejemplo perfectamente excelente.
¿Cómo se construye para Windows? Primero, borra Windows e instala Linux o BSD. };-)
. No, en realidad, sólo consulte la sección sobre la construcción para Windows en la introducción.
¿Cómo puedo compilar para Solaris/SunOS? Sigo recibiendo errores del enlazador cuando intento compilar?
Los errores del enlazador ocurren porque las cajas Sun no compilan automáticamente en las librerías de socket. Consulte la sección sobre compilación para Solaris/SunOS en la introducción para ver un ejemplo de cómo hacerlo.
¿Por qué select()
sigue cayendo en una señal?
Las señales tienden a hacer que las llamadas al sistema bloqueadas devuelvan -1
con errno
puesto a EINTR
. Cuando configure un manejador de señales con sigaction()
, puede establecer la bandera SA_RESTART
, que se supone que reinicia la llamada al sistema después de haber sido interrumpida.
Naturalmente, esto no siempre funciona.
Mi solución favorita para esto implica una goto
. Sabes que esto irrita mucho a tus profesores, así que ¡hazlo! así que ¡adelante!
select_restart:
if ((err = select(fdmax+1, &readfds, NULL, NULL, NULL)) == -1) {
if (errno == EINTR) {
// alguna señal acaba de interrumpirnos, así que reinicia
goto select_restart;
}
// maneja el error real aquí:
perror("select");
}
Claro, no necesitas usar goto
en este caso; puedes usar otras estructuras para controlarlo. Pero creo que la sentencia goto
es realmente más limpia.
¿Cómo puedo implementar un tiempo de espera en una llamada a recv()
?
Usa select()
! Te permite especificar un parámetro de tiempo de espera para los descriptores de socket de los que quieres leer. O bien, puede envolver toda la funcionalidad en una sola función, como esta:
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
int recvtimeout(int s, char *buf, int len, int timeout)
{
fd_set fds;
int n;
struct timeval tv;
// configurar el conjunto de descriptores de fichero
FD_ZERO(&fds);
FD_SET(s, &fds);
// configurar la estructura timeval para el tiempo de espera
tv.tv_sec = timeout;
tv.tv_usec = 0;
// espera hasta que se agote el tiempo o se reciban datos
n = select(s+1, &fds, NULL, NULL, &tv);
if (n == 0) return -2; // timeout!
if (n == -1) return -1; // error
// los datos deben estar aquí, así que haz un recv() normal
return recv(s, buf, len, 0);
}
.
.
.
// Ejemplo de llamada a recvtimeout():
n = recvtimeout(s, buf, sizeof buf, 10); // 10 second timeout
if (n == -1) {
// se ha producido un error
perror("recvtimeout");
}
else if (n == -2) {
// se ha agotado el tiempo de espera
} else {
// tengo algunos datos en buf
}
.
.
.
Notice that recvtimeout()
returns -2
in case of a timeout. Why not return 0
? Well, if you recall, a return value of 0
on a call to recv()
means that the remote side closed the connection. So that return value is already spoken for, and -1
means “error”, so I chose -2
as my timeout indicator.
How do I encrypt or compress the data before sending it through the socket?
One easy way to do encryption is to use SSL (secure sockets layer), but that’s beyond the scope of this guide. (Check out the OpenSSL project46 for more info.)
But assuming you want to plug in or implement your own compressor or encryption system, it’s just a matter of thinking of your data as running through a sequence of steps between both ends. Each step changes the data in some way.
send()
s encrypted dataNow the other way around:
recv()
s encrypted dataIf you’re going to compress and encrypt, just remember to compress first. :-)
Just as long as the client properly undoes what the server does, the data will be fine in the end no matter how many intermediate steps you add.
So all you need to do to use my code is to find the place between where the data is read and the data is sent (using send()
) over the network, and stick some code in there that does the encryption.
What is this “PF_INET
” I keep seeing? Is it related to AF_INET
?
Yes, yes it is. See the section on socket()
for details.
How can I write a server that accepts shell commands from a client and executes them?
For simplicity, lets say the client connect()
s, send()
s, and close()
s the connection (that is, there are no subsequent system calls without the client connecting again).
The process the client follows is this:
connect()
to serversend("/sbin/ls > /tmp/client.out")
close()
the connectionMeanwhile, the server is handling the data and executing it:
accept()
the connection from the clientrecv(str)
the command stringclose()
the connectionsystem(str)
to run the command Beware! Having the server execute what the client says is like giving remote shell access and people can do things to your account when they connect to the server. For instance, in the above example, what if the client sends “rm -rf ~
”? It deletes everything in your account, that’s what!
So you get wise, and you prevent the client from using any except for a couple utilities that you know are safe, like the foobar
utility:
if (!strncmp(str, "foobar", 6)) {
(sysstr, "%s > /tmp/server.out", str);
sprintf(sysstr);
system}
But you’re still unsafe, unfortunately: what if the client enters “foobar; rm -rf ~
”? The safest thing to do is to write a little routine that puts an escape (“\
”) character in front of all non-alphanumeric characters (including spaces, if appropriate) in the arguments for the command.
As you can see, security is a pretty big issue when the server starts executing things the client sends.
I’m sending a slew of data, but when I recv()
, it only receives 536 bytes or 1460 bytes at a time. But if I run it on my local machine, it receives all the data at the same time. What’s going on?
You’re hitting the MTU—the maximum size the physical medium can handle. On the local machine, you’re using the loopback device which can handle 8K or more no problem. But on Ethernet, which can only handle 1500 bytes with a header, you hit that limit. Over a modem, with 576 MTU (again, with header), you hit the even lower limit.
You have to make sure all the data is being sent, first of all. (See the sendall()
function implementation for details.) Once you’re sure of that, then you need to call recv()
in a loop until all your data is read.
Read the section Son of Data Encapsulation for details on receiving complete packets of data using multiple calls to recv()
.
I’m on a Windows box and I don’t have the fork()
system call or any kind of struct sigaction
. What to do?
If they’re anywhere, they’ll be in POSIX libraries that may have shipped with your compiler. Since I don’t have a Windows box, I really can’t tell you the answer, but I seem to remember that Microsoft has a POSIX compatibility layer and that’s where fork()
would be. (And maybe even sigaction
.)
Search the help that came with VC++ for “fork” or “POSIX” and see if it gives you any clues.
If that doesn’t work at all, ditch the fork()
/sigaction
stuff and replace it with the Win32 equivalent: CreateProcess()
. I don’t know how to use CreateProcess()
—it takes a bazillion arguments, but it should be covered in the docs that came with VC++.
I’m behind a firewall—how do I let people outside the firewall know my IP address so they can connect to my machine?
Unfortunately, the purpose of a firewall is to prevent people outside the firewall from connecting to machines inside the firewall, so allowing them to do so is basically considered a breach of security.
This isn’t to say that all is lost. For one thing, you can still often connect()
through the firewall if it’s doing some kind of masquerading or NAT or something like that. Just design your programs so that you’re always the one initiating the connection, and you’ll be fine.
If that’s not satisfactory, you can ask your sysadmins to poke a hole in the firewall so that people can connect to you. The firewall can forward to you either through it’s NAT software, or through a proxy or something like that.
Be aware that a hole in the firewall is nothing to be taken lightly. You have to make sure you don’t give bad people access to the internal network; if you’re a beginner, it’s a lot harder to make software secure than you might imagine.
Don’t make your sysadmin mad at me. ;-)
How do I write a packet sniffer? How do I put my Ethernet interface into promiscuous mode?
For those not in the know, when a network card is in “promiscuous mode”, it will forward ALL packets to the operating system, not just those that were addressed to this particular machine. (We’re talking Ethernet-layer addresses here, not IP addresses–but since ethernet is lower-layer than IP, all IP addresses are effectively forwarded as well. See the section Low Level Nonsense and Network Theory for more info.)
This is the basis for how a packet sniffer works. It puts the interface into promiscuous mode, then the OS gets every single packet that goes by on the wire. You’ll have a socket of some type that you can read this data from.
Unfortunately, the answer to the question varies depending on the platform, but if you Google for, for instance, “windows promiscuous ioctl” you’ll probably get somewhere. For Linux, there’s what looks like a useful Stack Overflow thread47, as well.
How can I set a custom timeout value for a TCP or UDP socket?
It depends on your system. You might search the net for SO_RCVTIMEO
and SO_SNDTIMEO
(for use with setsockopt()
) to see if your system supports such functionality.
The Linux man page suggests using alarm()
or setitimer()
as a substitute.
How can I tell which ports are available to use? Is there a list of “official” port numbers?
Usually this isn’t an issue. If you’re writing, say, a web server, then it’s a good idea to use the well-known port 80 for your software. If you’re writing just your own specialized server, then choose a port at random (but greater than 1023) and give it a try.
If the port is already in use, you’ll get an “Address already in use” error when you try to bind()
. Choose another port. (It’s a good idea to allow the user of your software to specify an alternate port either with a config file or a command line switch.)
There is a list of official port numbers48 maintained by the Internet Assigned Numbers Authority (IANA). Just because something (over 1023) is in that list doesn’t mean you can’t use the port. For instance, Id Software’s DOOM uses the same port as “mdqs”, whatever that is. All that matters is that no one else on the same machine is using that port when you want to use it.