What's new

iptables, ipsets and VPN 'kill switch'

  • SNBForums Code of Conduct

    SNBForums is a community for everyone, no matter what their level of experience.

    Please be tolerant and patient of others, especially newcomers. We are all here to share and learn!

    The rules are simple: Be patient, be nice, be helpful or be gone!

For preliminary testing, I put code in for DST for testing and commented out the rest in vpnrouting.sh.
Code:
######################## Xentrk updates for Martineau's awesome Web GUI IPSET features
# Proccess IPSET lists
# 'DST/SRC/DDD/SSS' 3-dimensional IPSET list
# 'SD/DS/DD/' 2-dimensional IPSET
#
     if [ "$TARGET_ROUTE" = "DST" ]; then
        ipset add OVPNC${VPN_UNIT} "$DESC"
        continue
#    elif [ "$TARGET_ROUTE" = "SRC" ]; then
#        ipset add OVPNC${VPN_UNIT} "$DESC"
#        continue
<snip>
######################################
The lists are deleted properly from the master IPSET list for the VPN Client interface when deleted in the web gui or the client is turned off. Everything went with out a hitch.
Here is how I use vpnrouting.sh to process the modified NVRAM/GUI to make it convenient to add the VPN fwmark tagging
Code:
if [ ! -z "$(echo $TARGET_ROUTE | grep -oE "SRC|DST|^D|^S")" ];then

 IPSET_NAME=$DESC

 # Allow for 2-dimension and 3-dimension IPSETs.....
 case $TARGET_ROUTE in         # TBA review static 'case' with a regexp? ;-)
    SRC|DST) DIM=(echo $TARGET_ROUTE | tr 'A-Z' 'a-z');;
    *) case $TARGET_ROUTE in
           DD)  DIM="dst,dst";;
           SS)  DIM="src,src";;
           DS)  DIM="dst,src";;
           SD)  DIM="src,dst";;
           DDS) DIM="dst,dst,src";;
           SSS) DIM="src,src,src";;
           SSD) DIM="src,src,dst";;
           DDD) DIM="dst,dst,dst";;
       esac
 esac

 # If the Source IP is a real LAN IP then include it in the IPSET fwmark rule
 
 <snip>
 
 # Validate that $IPSET_NAME does physically exist etc.
 
 <snip>
 
 # before tagging with appropriate VPN Client fwmark
 
 iptables -t mangle -D PREROUTING $SRC -i br0 -m set --match-set $IPSET_NAME $DIM -j MARK --set-mark $TAG_MARK/$TAG_MARK 2> /dev/null
 iptables -t mangle -A PREROUTING $SRC -i br0 -m set --match-set $IPSET_NAME $DIM -j MARK --set-mark $TAG_MARK/$TAG_MARK 2> /dev/null
 
fi
Your code snippet appears to add the IPSET name to the master OVPNCx set list - a very useful addition for my modified GUI.
So, perhaps you should alter the logic to assume if all three dimensions are blank, then the IPSET name should then be added to an existing master IPSET list?, or perhaps an additional GUI 'add' check box?
 
Here is how I use vpnrouting.sh to process the modified NVRAM/GUI to make it convenient to add the VPN fwmark tagging
Code:
if [ ! -z "$(echo $TARGET_ROUTE | grep -oE "SRC|DST|^D|^S")" ];then

 IPSET_NAME=$DESC

 # Allow for 2-dimension and 3-dimension IPSETs.....
 case $TARGET_ROUTE in         # TBA review static 'case' with a regexp? ;-)
    SRC|DST) DIM=(echo $TARGET_ROUTE | tr 'A-Z' 'a-z');;
    *) case $TARGET_ROUTE in
           DD)  DIM="dst,dst";;
           SS)  DIM="src,src";;
           DS)  DIM="dst,src";;
           SD)  DIM="src,dst";;
           DDS) DIM="dst,dst,src";;
           SSS) DIM="src,src,src";;
           SSD) DIM="src,src,dst";;
           DDD) DIM="dst,dst,dst";;
       esac
 esac

 # If the Source IP is a real LAN IP then include it in the IPSET fwmark rule
 
 <snip>
 
 # Validate that $IPSET_NAME does physically exist etc.
 
 <snip>
 
 # before tagging with appropriate VPN Client fwmark
 
 iptables -t mangle -D PREROUTING $SRC -i br0 -m set --match-set $IPSET_NAME $DIM -j MARK --set-mark $TAG_MARK/$TAG_MARK 2> /dev/null
 iptables -t mangle -A PREROUTING $SRC -i br0 -m set --match-set $IPSET_NAME $DIM -j MARK --set-mark $TAG_MARK/$TAG_MARK 2> /dev/null
 
fi
Your code snippet appears to add the IPSET name to the master OVPNCx set list - a very useful addition for my modified GUI.
So, perhaps you should alter the logic to assume if all three dimensions are blank, then the IPSET name should then be added to an existing master IPSET list?, or perhaps an additional GUI 'add' check box?
I was cleaning up my code from all of the changes this afternoon and realized I had some work to do. More and more of the code in my selective routing script is moving to vpnrouting.sh. Thank you for pointing me in the right direction.

As a proof of concept, I set up a one dimension IPSET list containing two lan clients. I then deleted the ip rules for the lan clients and created a new iptables entry to mark the packets based on src IP address.

Code:
Chain PREROUTING (policy ACCEPT 238K packets, 197M bytes)
num   pkts bytes target     prot opt in     out     source               destination
<snip>
6       83 10240 MARK       all  --  br0    *       0.0.0.0/0            0.0.0.0/0            match-set WAN0 dst MARK or 0x8000
7      375 61128 MARK       all  --  br0    *       0.0.0.0/0            0.0.0.0/0            match-set OVPNC1 dst MARK or 0x1000
8     3621  625K MARK       all  --  br0    *       0.0.0.0/0            0.0.0.0/0            match-set OVPNC2 dst MARK or 0x2000
9       95  9867 MARK       all  --  br0    *       0.0.0.0/0            0.0.0.0/0            match-set OVPNC3 dst MARK or 0x4000
10      82  9010 MARK       all  --  br0    *       0.0.0.0/0            0.0.0.0/0            match-set OVPNC4 dst MARK or 0x7000
11       0     0 MARK       all  --  br0    *       0.0.0.0/0            0.0.0.0/0            match-set OVPNC5 dst MARK or 0x3000
12   47134 4353K MARK       all  --  br0    *       0.0.0.0/0            0.0.0.0/0            match-set LAN_CLIENTS src MARK or 0x3000
I changed the packet marking to test each tunnel. Everything worked. I had issues with this in the past and now realize why. Having this work provides the flexibility to eliminate routing rules for lan clients and replace the routing with ipset lists/iptables. I'll implement the changes and report back.

This functionality helps make the firmware more user friendly. I thought of some other features that may be worth considering down the road
  • ability to create ipset lists thru the web gui by specifying ASN or, an http/https source.
  • give the user the ability to paste domain names inside a text box that would then be saved to a file. The system would then generate an ipset list from the list of domain names.
  • Similarly, provide the user the option to paste IPv4 address into a text box to create a custom ipset list.
  • Web gui interface to insert, update or delete "ipset=/" entries in dnsmasq.conf.add
This is a Great project!
 
@Martineau

Once again, I'm very grateful for the work you've done. I added the feature in vpnrouting.sh to create the iptables entries for the IPSET lists in addition to code for deleting any iptables entries for the IPSET lists when the interface tunnel goes down. I need to take a break and look at things with a fresh pair of eyes tomorrow.

I have a better understanding now of the gui changes & vpnrouting.sh interface features. I will update my other selective routing scripts and perform more testing tomorrow. My original selective routing script is now a shell of its former self. Most of the code has been commented out with the updates I made to vpnrouting.sh. I'm excited at the possibilities. Will be interesting to see how this turns out.
 
@Martineau

Once again, I'm very grateful for the work you've done. I added the feature in vpnrouting.sh to create the iptables entries for the IPSET lists in addition to code for deleting any iptables entries for the IPSET lists when the interface tunnel goes down. I need to take a break and look at things with a fresh pair of eyes tomorrow.

I have a better understanding now of the gui changes & vpnrouting.sh interface features. I will update my other selective routing scripts and perform more testing tomorrow. My original selective routing script is now a shell of its former self. Most of the code has been commented out with the updates I made to vpnrouting.sh. I'm excited at the possibilities. Will be interesting to see how this turns out.

Remember, you could retain your custom selective routing scripts and simply call them from vpnrouting.sh, rather than make major in-line changes within vpnrouting.sh otherwise you will need to ensure that any upstream edits to vpnrouting.sh in future firmware releases are identified and (if deemed applicable) manually merged with your code.
 
Hi all, I've spent a long time trying to find a way to policy route icmp packets (well, ping and traceroute, I know they tend to work a bit differently on different platforms) over either tun11, tun12 or ppp0 depending on which ipset contains the destination address. Hoping someone can point me in the right direction.

ping seems to be policy routing when it originates from a Mac client. But, when the ping originates from the router command line it defaults to using ppp0 (although I know you can use ping -I to specify the interface) so destination addresses that should be routed via tun11 or tun12 aren't reachable.

traceroute is somewhat policy routing when it originates from a Mac client. It works fine to tun11 and ppp0, but for tun12 it stops at the first hop after the router. But, when the traceroute originates from the router command line it defaults to using ppp0 (although I know you can use traceroute -i to specify the interface) so destination addresses that should be routed via tun11 or tun12 aren't reachable.

I must be missing something obvious (maybe it's not possible because of the two vpn connections?) so if anyone has any suggestions that'd be great.

My nat-start script is here. Thanks.

Code:
ip rule del fwmark 0x8000/0x8000
ip rule add fwmark 0x8000/0x8000 table main prio 9990
ip rule del fwmark 0x1000/0x1000
ip rule add fwmark 0x1000/0x1000 table ovpnc1 prio 9995
ip rule del fwmark 0x2000/0x2000
ip rule add fwmark 0x2000/0x2000 table ovpnc2 prio 9994

# VPN1
iptables -v -t mangle -D PREROUTING -p all -m set --match-set VPN1 dst -j MARK --set-mark 0x1000/0x1000
iptables -v -t mangle -A PREROUTING -p all -m set --match-set VPN1 dst -j MARK --set-mark 0x1000/0x1000
iptables -v -t mangle -D OUTPUT -p all -m set --match-set VPN1 dst -j MARK --set-mark 0x1000/0x1000
iptables -v -t mangle -A OUTPUT -p all -m set --match-set VPN1 dst -j MARK --set-mark 0x1000/0x1000
iptables -v -t nat -D POSTROUTING -s $(nvram get wan0_ipaddr) -o tun11 -j MASQUERADE
iptables -v -t nat -A POSTROUTING -s $(nvram get wan0_ipaddr) -o tun11 -j MASQUERADE

# VPN2
iptables -v -t mangle -D PREROUTING -p all -m set --match-set VPN2 dst -j MARK --set-mark 0x2000/0x2000
iptables -v -t mangle -A PREROUTING -p all -m set --match-set VPN2 dst -j MARK --set-mark 0x2000/0x2000
iptables -v -t mangle -D OUTPUT -p all -m set --match-set VPN2 dst -j MARK --set-mark 0x2000/0x2000
iptables -v -t mangle -A OUTPUT -p all -m set --match-set VPN2 dst -j MARK --set-mark 0x2000/0x2000
iptables -v -t nat -D POSTROUTING -s $(nvram get wan0_ipaddr) -o tun12 -j MASQUERADE
iptables -v -t nat -A POSTROUTING -s $(nvram get wan0_ipaddr) -o tun12 -j MASQUERADE

# WAN0 (traffic that must be forced to go via ISP)
iptables -v -t mangle -D PREROUTING -p all -m set --match-set WAN0 dst -j MARK --set-mark 0x8000/0x8000
iptables -v -t mangle -A PREROUTING -p all -m set --match-set WAN0 dst -j MARK --set-mark 0x8000/0x8000
iptables -v -t mangle -D OUTPUT -p all -m set --match-set WAN0 dst -j MARK --set-mark 0x8000/0x8000
iptables -v -t mangle -A OUTPUT -p all -m set --match-set WAN0 dst -j MARK --set-mark 0x8000/0x8000
#iptables -v -t nat -D POSTROUTING -s $(nvram get wan0_ipaddr) -o ppp0 -j MASQUERADE
#iptables -v -t nat -A POSTROUTING -s $(nvram get wan0_ipaddr) -o ppp0 -j MASQUERADE

iptables -N mylogdrop

iptables -D OUTPUT ! -o tun11 -p all -m set --match-set VPN1 dst -j mylogdrop
iptables -D OUTPUT ! -o tun12 -p all -m set --match-set VPN2 dst -j mylogdrop
iptables -D OUTPUT ! -o ppp0 -p all -m set --match-set WAN0 dst -j mylogdrop
iptables -D FORWARD ! -o tun11 -p all -m set --match-set VPN1 dst -j mylogdrop
iptables -D FORWARD ! -o tun12 -p all -m set --match-set VPN2 dst -j mylogdrop
iptables -D FORWARD ! -o ppp0 -p all -m set --match-set WAN0 dst -j mylogdrop

iptables -A OUTPUT ! -o tun11 -p all -m set --match-set VPN1 dst -j mylogdrop
iptables -A OUTPUT ! -o tun12 -p all -m set --match-set VPN2 dst -j mylogdrop
iptables -A OUTPUT ! -o ppp0 -p all -m set --match-set WAN0 dst -j mylogdrop
iptables -A FORWARD ! -o tun11 -p all -m set --match-set VPN1 dst -j mylogdrop
iptables -A FORWARD ! -o tun12 -p all -m set --match-set VPN2 dst -j mylogdrop
iptables -A FORWARD ! -o ppp0 -p all -m set --match-set WAN0 dst -j mylogdrop

iptables -D mylogdrop -j LOG --log-prefix "My IPTables dropped: " --log-level 4
iptables -A mylogdrop -j LOG --log-prefix "My IPTables dropped: " --log-level 4
iptables -D mylogdrop -j DROP
iptables -A mylogdrop -j DROP
 
@Martineau,

Everything is working with the changes to vpnrouting.sh to accommodate the IPSET set list mods to the OpenVPN Client GUI. The last item I need to code is to prevent the IPSET list from being added to the routing table (e.g. ip rule add ...) .

However, I didn't notice the $SRC parameter in the iptables code snip you posted until I finalized that part of the code.
Code:
 iptables -t mangle -D PREROUTING $SRC -i br0 -m set --match-set $IPSET_NAME $DIM -j MARK --set-mark $TAG_MARK/$TAG_MARK 2> /dev/null
 iptables -t mangle -A PREROUTING $SRC -i br0 -m set --match-set $IPSET_NAME $DIM -j MARK --set-mark $TAG_MARK/$TAG_MARK 2> /dev/null
Can you elaborate on the use case for the parameter? My guess is that your code is passing a connection protocol in some scenarios (e.g. -p tcp)???
 
I didn't notice the $SRC parameter in the iptables code snip you posted until I finalized that part of the code.
Code:
iptables -t mangle -A PREROUTING $SRC -i br0 -m set --match-set $IPSET_NAME $DIM -j MARK --set-mark $TAG_MARK/$TAG_MARK 2> /dev/null
Can you elaborate on the use case for the parameter? My guess is that your code is passing a connection protocol in some scenarios (e.g. -p tcp)???

The $SRC variable is derived from the GUI field (as shown below) and is passed as normal to vpnrouting.sh

upload_2019-3-12_12-43-9.png


and simply allows IPSET routing (if specified) from a specific LAN device (or subnet), otherwise $SRC is NULL and means the Selective IPSET routing rule applies to ALL LAN devices.
 
The $SRC variable is derived from the GUI field (as shown below) and is passed as normal to vpnrouting.sh

View attachment 16542

and simply allows IPSET routing (if specified) from a specific LAN device (or subnet), otherwise $SRC is NULL and means the Selective IPSET routing rule applies to ALL LAN devices.
Thank you. That helps clarify. I will add it back to the code and test the function tomorrow. I was always entering a valid "dummy" IP address but had removed the $SRC reference for the iptables code.

For the past hour or so, I was trying to find where the validation on the Source IP address is being done for IPSET lists as I noticed the GUI allows an invalid IP address to be entered for IPSET lists. Do you know where the onClick="addRow_Group2(100) function is on the file system?

Code:
<input type="button" class="add_btn" onClick="addRow_Group2(100);" value="">

For example, I was able to enter the value of all "1" in the Source IP field.
 
For the past hour or so, I was trying to find where the validation on the Source IP address is being done for IPSET lists as I noticed the GUI allows an invalid IP address to be entered for IPSET lists.
Code:
<input type="button" class="add_btn" onClick="addRow_Group2(100);" value="">
For example, I was able to enter the value of all "1" in the Source IP field.
Lazily, my quick'n'dirty .html hacks to create a proof-of-concept IPSET Selective GUI interface lacks 'polish'

I'm not a fully trained HTML/.asp programmer, and the weird Asus dynamic HTML server concept isn't something I need to really learn!:p

So validating text input fields wasn't deemed a priority :oops:, but to fix your issue you need to include the same field validation that is applied to GUI input field clientlist_ipAddr

e.g. Modify Advanced_OpenVPNClient_Content.asp
Code:
<input type="text" class="input_18_table" maxlength="18" name="clientlist_IPSETipAddr">
change to
Code:
<input type="text" class="input_18_table" maxlength="18" name="clientlist_IPSETipAddr" onKeyPress="return validator.isIPAddrPlusNetmask(this, event)" autocomplete="off" autocorrect="off" autocapitalize="off">
but it stlll isn't 100% foolproof...i.e. although it now correctly (automatically) creates/allows dotted decimal but doesn't reject a badly formed CIDR subnet entry and still allows
Code:
111.111.111.111111
:eek:
Do you know where the onClick="addRow_Group2(100) function is on the file system?
I simply cloned the existing addRow_Group() function inline within Advanced_OpenVPNClient_Content.asp to create the custom GUI IPSET processing, but I carelessly omitted the following field value validation :oops::oops: in addRow_Group2()
Code:
if (!validator.ipv4cidr(document.form.clientlist_IPSETipAddr)) {
document.form.clientlist_IPSETipAddr.focus();
document.form.clientlist_IPSETipAddr.select();
return false;
}

Hopefully these two hacks should eliminate IPSET GUI data entry errors, but further validation could be done say to ensure that the IPSET name entered physically exists?, although (as it is easier) using a script (vpnrouting.sh etc.) which should ultimately validate all passed args anyway?
 
Last edited:
The $SRC variable is derived from the GUI field (as shown below) and is passed as normal to vpnrouting.sh

View attachment 16542

and simply allows IPSET routing (if specified) from a specific LAN device (or subnet), otherwise $SRC is NULL and means the Selective IPSET routing rule applies to ALL LAN devices.

Thanks again for the help. I was able to come up with a solution to check the value of SRC IP address, which is derived from $VPN_IP, to make sure it is valid LAN IP address. And, if so, set the value.

Code:
LAN_IP=$(nvram get lan_ipaddr)
DEST_IP="$VPN_IP"
SRC=""

lanip_oct1=$(echo $LAN_IP | cut -d "." -f1)
lanip_oct2=$(echo $LAN_IP | cut -d "." -f2)
lanip_oct3=$(echo $LAN_IP | cut -d "." -f3)
lanip_oct4=$(echo $LAN_IP | cut -d "." -f4)

# Set SRC parm for iptables command if SRC ip address is a valid LAN IP

if [ ! -z $(echo $DEST_IP | grep -Eo '(([0-9]{1,3})\.){3}([0-9]{1,3}){1}' | grep -vE '25[6-9]|2[6-9][0-9]|[3-9][0-9][0-9]') ]; then
    srcip_oct1=$(echo $DEST_IP | cut -d "." -f1)
    srcip_oct2=$(echo $DEST_IP | cut -d "." -f2)
    srcip_oct3=$(echo $DEST_IP | cut -d "." -f3)
    srcip_oct4=$(echo $DEST_IP | cut -d "." -f4)

    if [ "$srcip_oct1" -eq "$lanip_oct1" ]; then
        if [ "$srcip_oct2" -eq "$lanip_oct2" ]; then
            if [ "$srcip_oct3" -eq "$lanip_oct3" ]; then
                if [ "$srcip_oct4" -gt 1 ] && [ "$srcip_oct4" -le 254 ]; then
                    SRC="-s $DEST_IP"
                fi
            fi
        fi
    fi

fi

I'll look into the GUI edit after I do some code clean up.

I also need to decide if I want to keep a custom function I created. The custom function allows to a user to configure routing using a text file by domain name:

Code:
#########################################################
# Assign the interface for each destination by entering
# the appropriate interface number in the first column
# 0 = WAN                                            
# 1 = OVPNC1                                    
# 2 = OVPNC2                                      
# 3 = OVPNC3                                    
# 4 = OVPNC4                                        
# 5 = OVPNC5                                         
#########################################################
0 wunderground.com
0 whatismyip.network
1 ipinfo.info
2 detectmyip.net
3 ipaddress.com
4 whatismypublicip.com
5 whatismyipaddress.com

Something like this can be used to validate the end point of each iface or for one off special routing of a website. I will also need to provide the feature to specify the rule just for a LAN ip address as it only applies the rule to all LAN clients.
 
Last edited:

Latest threads

Support SNBForums w/ Amazon

If you'd like to support SNBForums, just use this link and buy anything on Amazon. Thanks!

Sign Up For SNBForums Daily Digest

Get an update of what's new every day delivered to your mailbox. Sign up here!
Top