USAGE
You have Linux router and, while you want to deny your users internet access, you also want to display them page about that. You want to deny your users access to certain sites and, instead of desired site, show them page with warning/notification that they've been naughty little users or that that site is forbidden. Other weird ideas...
BASIC STUFF
Ok, I have small server which (mainly) serves as a router for about 20 users/neighbours and we share internet. Problem is when someone don't want internet for upcoming month or is not there or simple when someone forgets to chip in for internet. Until now, we had simple iptables solution - REJECT all packets and allow only IP addresses (or MAC) that paid for internet with something like this:iptables -I FORWARD -i ext0 -o ext1 -j DROPThen we got the idea that, when someone who didn't pay for internet visit some site get simple page (from ours Apache) telling him that he didn't paid (until now, he only could see his browser stuck while resolving site's hostname). First idea was to hack our server's DNS service, but it would be hell to do, nonextensible and, well...shitty.
iptables -I FORWARD -p ALL -s 192.168.0.XX -j ACCEPT
iptables -I FORWARD -p ALL -d 192.168.0.XX -j ACCEPT
So, here is simple, yet effective solution. General idea is to redirect http request packets to our server and show them simple notification page. So, first we will need is iptables stuff. Simple line that redirect specific user would be:
iptables -t nat -I PREROUTING -s 192.168.0.XX -p tcp --dport 80 -j REDIRECT --to-ports 3001Quick explanation is that all TCP packets from user with IP 192.168.0.XX destined to port 80 would get redirected to port 3001 of our server. This is where we will be putting our apache server to listen (later on). But, hey...wait...this isn't going to work. Why? Because, when user requests (for example) http://www.mozilla.com/en-US/firefox, hostname should first be resolved, e.g. before any packets on port 80 is sent, packets to DNS server is sent. And, if we have:
iptables -I FORWARD -i ext0 -o ext1 -j DROPthen there would be no resolving, no http packets. So, we must allow DNS packets with something like this:
iptables -I FORWARD -s 192.168.0.XX -p udp --dport 53 -j ACCEPTThis, in some way is trade-off since our users are now not completely isolated from internet - they got access to DNS servers. OK, first part with redirecting is over - next to setting up apache.
iptables -I FORWARD -d 192.168.0.XX -p udp --sport 53 -j ACCEPT
iptables -t nat -I PREROUTING -s 192.168.0.XX -p tcp --dport 80 -j REDIRECT --to-ports 3001
All you need now is to create new virtual host on Apache. YMMV, but here's all I had to do in httpd.conf to make it work:
Listen 192.168.0.1:3001Append it to end of httpd.conf, and make new htdocs root folder (here is /var/www/no_net/) with index.html which will show notification that user didn't pay for internet. Port for listening must be same as in iptables redirect command (3001) on which this virtual host work. As you can see, listen directive is only for localhost and our LAN, so this virtual host is not visible from internet. If you look carefully in this VirtualHost directive, you could notice two strange lines:
Listen 127.0.0.1:3001
NameVirtualHost *:3001
<VirtualHost *:3001>
ServerName <ours_servers_LAN_name>
DocumentRoot /var/www/no_net/
RewriteEngine On
RewriteRule ^.* /index.html
<VirtualHost>
RewriteEngine OnThese lines rewrite everything in URL and replaces it with "/index.html". Why? Because, let's suppose user goes to the site http://www.mozilla.com/en-US/firefox/.
RewriteRule ^.* /index.html
Then it got redirected to 3001 port. From there, user's browser asks for /en-US/firefox/ page which, of course don't exist. Easiest way to gather all GET request is to rewrite them all with mod_rewrite module. Second line does exactly that - with help of regular expression, it replaces anything with "/index.html". All you have to do now is to create index.html page and that is it.
But, hey - this is too simple to be geeky. Let's spice it with some Bash magic.
ADVANCED STUFF
Since our local network is on 192.168.0.0/24 subnet, we have 253 available IPs and around 20 users only. So, basically you will have to repeat 253-20 times:iptables -I FORWARD -s 192.168.0.XX -p udp --dport 53 -j ACCEPTand 20 times part:
iptables -I FORWARD -d 192.168.0.XX -p udp --sport 53 -j ACCEPT
iptables -t nat -I PREROUTING -s 192.168.0.XX -p tcp --dport 80 -j REDIRECT --to-ports 3001
iptables -I FORWARD -p ALL -s 192.168.0.XX -j ACCEPTHuh. Either to have a slave to do work for you, or to learn bash.
iptables -I FORWARD -p ALL -d 192.168.0.XX -j ACCEPT
So, here's how I did it. This is no extravagant solution, but will give you idea. Create file /etc/allowed_ips and in it, you put last IP octet of IP's that will have internet - one octet per line. So, if 192.168.0.12 and 192.168.0.34 have internet, this file will look like:
1
12
34
Our subnet is static, and for the sake of simplicity, I ommited whole IP addresses. Also, notice that we need to enable server itself (which is 192.168.0.1). Now, create script ip_allowing (put it wherever you want) with content like this:
#!/bin/bashNo voodoo magic here. Function check_exist takes number (last octet) as argument, and checks whatever that IP is allowed and returns 1 if it is, 0 if it isn't. Main part just goes in loop from 1 to 254, and either allow DNS and redirect or allows all packets. You might have noticed line:
FILE=/etc/allowed_ips
ip_listing=`cat $FILE`
function check_exist {
for i in $ip_listing; do
if [ ${i:0:1} != "#" ]; then
if [ "$i" = "$1" ]; then
return 1
fi
fi
done
return 0
}
for ip in `seq 1 254`; do
check_exist $ip
if [ $? -eq 1 ]; then
#ip exist
iptables -I FORWARD -p ALL -s 192.168.0.$ip -j ACCEPT
iptables -I FORWARD -p ALL -d 192.168.0.$ip -j ACCEPT
echo allowing 192.168.0.$ip
else
#ip doesn't exist
iptables -I FORWARD -s 192.168.0.$ip -p udp --dport 53 -j ACCEPT
iptables -I FORWARD -d 192.168.0.$ip -p udp --sport 53 -j ACCEPT iptables -t nat -I PREROUTING -s 192.168.0.$ip -p tcp --dport 80 -j REDIRECT --to-ports 3001
fi
done
if [ ${i:0:1} != "#" ]; then
This line allows us to add comments to /etc/allowed_ips file, and ignore them, so our file could be like this now:
#serverPretty handy, isn't it. All what is left is to call our new script (and give it exec permission, but you all know that - otherwise you wouldn't go this far in reading) from wherever script you setup iptables. Example would be:
1
#neighbour 1
12
#neighbour 2
34
iptables -I FORWARD -i ext0 -o ext1 -j DROP
/etc/rc.d/ip_allowing
Where now?
Well, this is simple solution. There are a lot of things that can be changed. For example, you might want to allow and deny internet access by MAC, not by IP addresses. Or, if you have larger subnet, this could be slower, so you would want your iptables commands to work with IP ranges. Or, you could pull your allowed IP addresses not from file, but from LDAP or MySQL with a bit more of parsing. You could also redirect only certain sites by using both source and destination in iptables REDIRECT command. On the other hand, looking at apache part, you see that our virtual host is hungry - it eats all URLs, so you couldn't have two pages in it. If you want that (for example to have index.html and contact.html), you would have to play with regular expressions to replace everything except "/contact.html" to /index.html.Possibilities are endless, as with everything with Linux, you just have to know what you want and how to do it - simple as that.