So you have this Linux router - firewall with a basic ruleset, and you want to add some rules to make it more secure : allow only traffic to specified DNS servers (in stead of allow all DNS traffic), or specify rules where you need to include the external IP address of your router. That's quite simple, except if these addresses are assigned through DHCP, like when you have a cheap internet account : no fixed address.
In theory, once your ISP has assigned an address, it is not likely to change soon (your computer will ask to extend its DHCP lease before it expires), but at least it would be nice if the firewall rules get adapted to the dynamically assigned addresses whenever you run the script (eg. during the boot)
The solution is to interrogate the operating system and get these addresses. The IP address of your external network interface can be found with the ifconfig command. The ifconfig command returns the complete configuration of the network card, including subnetmask, packets sent and received, etc.
#ifconfig eth0
eth0 Link encap:Ethernet HWaddr 00:20:AF:48:1F:4A
inet addr:193.252.81.136 Bcast:255.255.255.255 Mask:255.255.240.0
RX packets:316745 errors:163 dropped:0 overruns:164 frame:163
TX packets:117968 errors:0 dropped:0 overruns:0 carrier:0
Interrupt:5 Base address:0x220
So, here you have to 'read' the output, and look for the part that says' inet addr:[something that looks like an IP address]' (so that the broadcast address and subnet mask are excluded), then read the address from the 'inet addr: ...' string.
A human can do that on sight, but your computer does not understand what it reads, so you make it look for patterns that it can recognise - e.g. an ip address (version 4) looks like
[number between 1 and 254].[number between 0 and 254].[number between 0 and 254].[number between 1 and 254]
These patterns are called 'regular expressions' - or RegExps.
A RegExp to describe an IP address could be : [0-9][0-9]*.[0-9][0-9]*.[0-9][0-9]*.[0-9][0-9]* : "4 groups of 1 to 3 digits, separated by dots". This does not exactly match our description of a valid IP address, eg. 0.666.999.11 matches the expression but can never be an IP address - but as we expect ifconfig to return valid addresses, that's not too much of a problem. Also : repeating the same part ([0-9][0-9]*) 4 times is also a regular pattern, so the expression could probably be made shorter. And : in regexp, the dot (.) is a wild card : it represents 'any character'. So to specify that it needs to be a dot and nothing else, you'd have to write \. .
Again, for the IP-address this doesn't matter : the string "inet addr:193.250.81.136" will match and nothing else in the output of ifconfig will, so it works.
So we can now recognize [something that looks like an ip addresses] in the output of ifconfig. To 'read' the output of ifconfig, we use grep. grep has options to return the pattern we're looking for, or the complete line on which the pattern occurs, or the name of the file in which a given pattern occurs, etc. we use '-o' : show only the part that matches PATTERN. So,
So, here you have to 'read' the output, and look for the part that says 'inet addr:[something that looks like an IP address]' (so that the broadcast address and subnet mask are excluded), then read the address from the 'inet addr: ...' string.
That would translate to something like :
#-all in one line !!- WAN_IP=`ifconfig eth0 | grep -o 'inet addr:[0-9][0-9]*.[0-9][0-9]*.[0-9][0-9]*.[0-9][0-9]*' | grep -o '[0-9][0-9]*.[0-9][0-9]*.[0-9][0-9]*.[0-9][0-9]*'` #check echo the public IP address of this machine is $WAN_IP
The DNS servers can be found in the /etc/resolv.conf file, etc. If we want to create a rule our (personal) firewall should only allow DNS traffic from it's own external IP address to these DNS servers (in stead of allowing a more general : allow all DNS because we need it), we could read the /etc/resolv.conf file (with 'cat'), and create a rule for each IP address found there. Finding the IP addresses is done by pattern matching as demonstrated with the WAN_IP example.
for NAMESERVER in `grep -o '[0-9][0-9]*.[0-9][0-9]*.[0-9][0-9]*.[0-9][0-9]*' /etc/resolv.conf`; do
iptables -A OUTPUT -d $NAMESERVER -s $WAN_IP -p udp --dport 53 -j ACCEPT
iptables -A INPUT -s $NAMESERVER -d $WAN_IP -p udp --sport 53 -j ACCEPT
done
This is a poor example of regular expressions, in fact it's just a first attempt to see how they work. There may be better patterns to match an IP address.
And as for the firewall rules, using the interface name instead of the address may also work - so you don't need to go through all that trouble :-)
Still, could be good to know something like this exists ...
an alternative way to extract the ip address from the output of ifconfig is by using 'cut'. Cut works on 'fields" based on delimiters (-d):
## masquerading to the WAN address of the router IF_WAN=$( ifconfig eth0 | grep "inet addr" | cut -d':' -f2 | cut -d' ' -f1 - ) iptables -t nat -A POSTROUTING -o $IF_WAN -j MASQUERADE
Because a firewall script is just a shell script that calls iptables to add rules, you can use variables and control structures such as loops and conditions to make it do almost anything. here's an example.