Introduction
The impatient can not read these lyrical digressions.Some time ago the thought came to me: “How can I do so in order to block access to the Internet (or to a particular host) to any one program in Linux?”. This thought visited me and flew further on its business. And today I received
one question on askdev.ru in my RSS reader. Bah! Yes, this is exactly what I thought! It is necessary to help the person, at the same time and most understand the issue.
I went to Google to see if there were any hints on the decision. From there I learned that it has become impossible to do regular
iptables for some time now, and people recommend looking towards
AppArmor . “Burning with a great desire to study
AppArmor, ” I began to search further and almost accidentally stumbled upon a
message on LOR , in which a rather interesting method was described.
')
Method
It consisted in “replacing” the connection function with its own, which decides whether to allow the connection and “skip” the request to this function or not - return an error. The message of the highly respected Chaoser used a low-level blocking of sockets, which made it impossible to make a decision depending on the server address and / or port. It did not suit me, I needed to deny access only for one port - the 80th. When I started
telnet , under
strace , I almost immediately saw a suitable victim, the
connect function.
strace described it as follows:
connect(3, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("87.250.251.3")}, 16) = 0
This description clearly shows that I have all the components I need: an IP address, a port, and a connection type (AF_INET).
Well, let's get started.
Decision
To implement this method, we will write a library that will load before others when you start the application, and thus intercept the functions that are defined in it (and which are intended for other libraries).
To begin with I will lay out all the code, and then we will analyze all separately.
- #define _GNU_SOURCE
- #include <dlfcn.h>
- #include <stdio.h>
- #include <sys / types.h>
- #include <sys / socket.h>
- #include <netinet / in .h>
- #include <arpa / inet.h>
- #include <errno.h>
- static int (* real_connect) ( int sockfd, const struct sockaddr * addr,
- socklen_t addrlen) = 0;
- int connect ( int sockfd, const struct sockaddr * addr,
- socklen_t addrlen)
- {
- printf ( "NF_DEBUG: --------------------------------------------- - \ n " );
- int sa_family = addr-> sa_family;
- printf ( "NF_DEBUG: Address family:% d (AF_INET =% d) \ n" , sa_family, AF_INET);
- if (sa_family == AF_INET)
- {
- struct sockaddr_in * addr_in = ( struct sockaddr_in *) (addr);
- struct in_addr sin_addr = addr_in-> sin_addr;
- uint16_t sin_port = addr_in-> sin_port;
- uint16_t sin_port_h = ntohs (sin_port);
- printf ( "NF_DEBUG: IP:% s \ n" , inet_ntoa (sin_addr));
- printf ( "NF_DEBUG: Port:% d \ n" , sin_port_h);
- if (sin_port_h == 80)
- {
- printf ( "NF_DEBUG: Rejected! \ n" );
- printf ( "NF_DEBUG: --------------------------------------------- - \ n " );
- errno = ENETUNREACH;
- return -1;
- }
- }
- if (! real_connect)
- real_connect = dlsym (RTLD_NEXT, "connect" );
- printf ( "NF_DEBUG: Accepted \ n" );
- printf ( "NF_DEBUG: --------------------------------------------- - \ n " );
- return real_connect (sockfd, addr, addrlen);
- }
* This source code was highlighted with Source Code Highlighter .
Parsing code
Those who have understood how the above program works can skip this section absolutely calmly. It will describe what line it does. This should be useful for beginners. Those who know C well may be interested in only a few points.Lines 1-8 are preprocessor directives, there is nothing interesting about them. Is that the directive
#define _GNU_SOURCE , which connects the GNU extensions necessary for the
dlsym function.
In lines 10-11, we declare a pointer to the "real" function connect. We will have it called
real_connect . The description of the function is taken from
man connect .
Line 13 already starts a new function
connect , which applications will call and which will decide whether to skip this application or not. Her description is fully consistent with the original
connect and taken from the same manual.
In the 17th line of the structure that contains all the data we need (and the description of which I took
here ) we get the type of address.
If the address type is AF_INET (line 20), that is, the application “asks for it outside” via IPv4, then we apply filtering (lines 22-37), otherwise, we immediately skip this connection to the “real”
connect function.
In order to filter the connections, you need to convert the
addr structure to the
sockaddr_in type, which allows you to access the required fields. This happens on line 22.
Further, in lines 24-25, we obtain the values of the address and port, respectively. In line 26 we change the order of the bytes in the port number, thereby obtaining the usual format of the port number (80, 110, 25) from the format that is used during the connection.
In line 28, the resulting address is reduced to textual form and output it, in the 29th line we display the port number.
Next, in the 31st line, we check if the port number matches the one we want to block, if so, we report this (lines 33-34), set the error number (line 35; in this case there will be an error "Network unavailable") and return an error (line 36).
If everything is in order, that is, the application has addressed using either an allowed port or an allowed address type, then we get the address (entry point) of the “real”
connect function (line 42), if we have not received it before (line 41; we forget that
real_connect is a global variable), we write that everything is fine (lines 44-45), and pass control to the “real”
connect function.
When getting the address of the “real”
connect function, we use the
RTLD_NEXT parameter to get this address from the next loaded library, and not from the first (our) one, otherwise we get infinite recursion.
Using
Compile library:
gcc -fPIC -shared -Wl,-soname,nonet.so -o nonet.so nonet.c
and launch the desired application as follows:
LD_PRELOAD=/< >/nonet.so <>
For example, if the library is in
/ tmp , then the following line can be used to launch
firefox without access to the Internet (of course, only through port 80):
LD_PRELOAD=/tmp/nonet.so firefox
Conclusion
This method in any case does not claim to be universal and all-applicable, as well as the article on the Nobel Prize. Here I just covered two topics that are interesting to me, and, I hope, will be interesting to someone else: restricting access to the network to certain programs and intercepting function calls in Linux.
Thanks for attention!
PS In order not to be considered a dishonest person who stole the code from digital'c askdev.ru, I think it should be mentioned that digital and I are one person.
UPD Thank you very much for inviting haber
man peter23 . Transferred a post to the Linux for All blog.