-
Notifications
You must be signed in to change notification settings - Fork 6
Using ipset with dnsmasq and iptables
One of the most common uses of ipset is being able to create DNS based rulesets in conjunction with dnsmasq and iptables. Typically, this often required when you want to selectively route certain websites differently i.e. bypass a VPN. Being able to use DNS based rules is much more maintainable and friendly when services like Netflix who utilises massive CDN networks make obtaining all ASN/IP ranges pretty much impossible.
ipset is also a more elegant way of storing single IPv4 or IPv6 ranges, compared to vanilla iptables.
First an ipset ruleset must exist to essentially hold any IPv4/IPv6 addresses you want to route selectively. This can be done like so:
# IPv4 example
ipset create EXAMPLE_BYPASS_V4 hash:ip family inet
# IPv6 example
ipset create EXAMPLE_BYPASS_V6 hash:ip family inet6
hash:ip will essentially store individual IPv4 or IPv6 of any matching ipset rules.
Example:
A request made to whatsmyip.org would return 208.64.38.55 and this would be written to the EXAMPLE_BYPASS_V4 ruleset if it was matched in an ipset policy.
In dnsmasq, you reference an ipset policy like so:
ipset=/domain.com/anotherdomain.com/somethingelse.org/EXAMPLE_BYPASS_V4,EXAMPLE_BYPASS_V6
Each domain required to be part of this policy is split via the / character. An added bonus for ipset is by adding the top level domain of any website, it will also match any subdomains or requests under the top level domain, so you don't have to explicitly define each individually. Bonus.
While a lot of traffic on the internet is still IPv4, you shouldn't forget to include IPv6 also, if you want to route certain IPv6 traffic differently also, but its not strictly required.
You then assign the domains in the ipset policy to a specific ruleset, each ipset policy can be assigned to multiple rulesets. Notice in the example both IPv4 and IPv6 policies are used.
Using iptables, you'll now want to mark traffic within the ipset policy to be treated differently by your router, this can be achieved with these rules. You will need a recent version of iptables installed for the --match-set directive to work, you will also need the ensure the mangle table is available on your router. For IPv4 it should already be loaded. For IPv6 you may have to load the ip6t_mangle module.
# IPv4 example
iptables -I PREROUTING -t mangle -m set --match-set EXAMPLE_BYPASS_V4 dst -j MARK --set-mark 1
# IPv6 example
ip6tables -I PREROUTING -t mangle -m set --match-set EXMPLE_BYPASS_V6 dst -j MARK --set-mark 1
The mark value of 1 this will be used later on we setup a specific routing table for this traffic.
With everything in place you need to now create a specific route for this marked traffic. A very basic example of how to do this is below:
# IPv4
ip rule add prio 100 fwmark 1 lookup 100
ip route add table 100 default dev $(nvram get wan_ifname)
# IPv6
ip -6 rule add prio 100 fwmark 1 lookup 100
ip -6 route add table 100 default dev $(nvram get wan_ifname)
wan_ifrname is a NVRAM variable on DD-WRT routers which provides the WAN interface i.e. the connection to your ISP. Depending on your router, this interface name might be different, commonly its vlan2 but that's typically for Broadcom based routers, hence using the NVRAM variable is safer. Essentially we are routing any traffic marked with the fwmark value of 1 to the WAN interface of your router.
ipset rules are saved in memory, upon a reboot they will be lost, in order to ensure you keep your ipset rules, you can run:
ipset save > /tmp/ipset.rules
This will write all currently rules stored IP address values into a file. To restore them you reverse the command:
ipset restore < /tmp/ipset.rules
When writing the file you will obviously want to store it on persistent storage such as JFFS or a USB device formatted with EXT2/3 or FAT.
Adding the save command to your shutdown script is probably a good idea, such a save action will only work when a graceful shutdown or reboot occurs, if for example an unexpected power loss occurs, the shutdown script would not run. Instead, it might also be a good idea to periodically have the ipset save command run as a cronjob, overwriting the file at fairly regular intervals.
In your startup script you can then add the ipset restore command so the rules are reloaded on reboot. You should ensure that the kernel module xt_set.ko is loaded and all ipset based commands are available in the boot process before running any commands related to it otherwise the restore is likely to fail.