The content of the article
Linux provides a huge set of routing functions and configuration tools. Experienced sysadmins know this and use the Linux arsenal to its fullest. But many, even advanced users, have no idea how much convenience all these wonderful features can bring. Today we will create routing tables and describe the rules for traversing them, as well as automate the administration of these tables. So, our creative plans:
What do we need to make everything work, and preferably comfortable? Of course, iptables, where can we go without it. Also iproute2, it will allow us to create a bunch of tables. IPSet will be required in order not to fence the garden with many iptables rules.
What is what
Briefly about two options
The client and server will establish an encrypted communication channel on port 443 (stunnel) and transmit inside OpenVPN on port 995. From the outside, it should look like normal HTTPS.
And here you can implement two connection schemes.
Option 1. On the client machine, iptables will mark packets with a certain flag if the addresses in the packets and IPSet lists match, and send them for routing.
Further, the packets marked with the flag will be sent according to the previously created routing table, the rest will follow the default route. With the help of the console or a small script, we will correct this list of addresses. There may be several lists depending on the situation.
Option 2. A host on the local network (virtual machine, Raspberry Pi or some other device) will act as a client. It will be listed as the default gateway on computers that need to access resources via VPN. Upon receiving a request for an IP address from the list, the gateway will enable NAT and send such traffic to the VPN. The rest of the traffic will be routed to the default gateway without NAT.
For Linux systems, we can leave the default gateway and install IPSet and iproute2, and then configure them similarly to the settings of the "intermediate" host router. In this case, at the client level, traffic will be selected according to the same IPSet list. That is, what is on the list will be sent to the intermediate host router and then to the VPN. The rest will be routed by default.
Implementation
Suppose that somewhere far away in the cloud we already have a VPS server running Ubuntu or Debian. In other distributions, the differences will most likely only be in the installation of the necessary packages. This host in our configuration will be used as the VPN server. There are a lot of recommendations on which VPS to use on the Internet - for different budgets, with different configurations and conditions.
Install OpenVPN, stunnel, git on the server:
Code:
sudo apt install openvpn stunnel4 git
Next, we will use the ready-made script to configure the OpenVPN server. You can, of course, configure everything manually, and I advise you to do just that. Information on how to properly configure OpenVPN is easy to find on the Web. But if you need to get the result very quickly and you are too lazy to mess around, then here is the solution.
Code:
git clone https://github.com/Nyr/openvpn-install.git
cd openvpn-install
sudo ./openvpn-install.sh
We get the installation script and run it. It will help us create user keys and certificates, if necessary, change the network addressing. Everything is very accessible and understandable. At the output, for each user, we will receive one file, inside which there will be a configuration key and certificates (server and user). We transfer this file to the client.
Open the OpenVPN server configuration file and edit it. We select the required port - the one that will be free on the server for connection. It is better to use a more or less inconspicuous port (I chose 995, usually mail ports are left open) so that the client can be guaranteed to connect to the VPN server.
This is how our config will look like:
Code:
port 443
proto tcp
dev tun0
sndbuf 0
rcvbuf 0
ca ca.crt
cert server.crt
key server.key
dh dh.pem
auth SHA512
tls-auth ta.key 0
topology subnet
server 10.8.0.0 255.255.255.0
ifconfig-pool-persist ipp.txt
push "redirect-gateway def1 bypass-dhcp"
push "dhcp-option DNS 208.67.222.222"
push "dhcp-option DNS 208.67.220.220"
push "route 10.8.0.0 255.255.255.0"
route 192.168.31.0 255.255.255.0
keepalive 10 120
cipher AES-256-CBC
user nobody
group nogroup
persist-key
persist-tun
status openvpn-status.log
verb 3
crl-verify crl.pem
The next step is to set up stunnel:
Code:
sudo nano /etc/stunnel/stunnel.conf
output = /var/log/stunnel.log
[openvpn]
client = no
accept = 443
connect = localhost:443
cert = /etc/stunnel/stunnelsrv.pems
Everything is simple here:
Code:
sudo openssl req -nodes -new -days 365 -newkey rsa:1024 -x509 -keyout stunnelsrv.pem -out stunnel.pem
At this stage, it is important to remember that on the server side we have OpenVPN running on port 995, proto tcp, dev tun0 and stunnel on port 443. All other settings are standard.
Moving on to the client:
Code:
sudo apt install iproute2 ipset stunnel4 git openvpn
We've installed everything we need, now we configure stunnel on the client side:
Code:
output = /var/log/stunnel.log
[openvpn]
client = yes
accept = 127.0.0.1:995
connect = IP:443
cert = /etc/stunnel/stunnel.pem
Here we indicate where to output the logs, client mode is enabled. With the help of the directive, acceptwe say to which address and port to send the connection and where, in fact, to connect to the stunnel client (option connect). Well, what certificate to use.
We transfer the OpenVPN client file from the server. We check the settings: port 995 is used, proto tcp, dev tun0. A very important point: we need to declare a route to our VPS through the default gateway. Otherwise, it turns out that stunnel will establish a connection, then the VPN will connect and try to redirect everything to the tunnel, but stunnel will no longer be able to work, since the host and port will be inaccessible. We also disable the redirection of all traffic to the VPN, check the connection address and port. The VPN server address in the config will be 127.0.0.1 - that is localhost. It is assumed that stunnel has already forwarded the correct client port. You should get something like the following:
Code:
client
dev tun
proto tcp
sndbuf 0
rcvbuf 0
remote 127.0.0.1 995
resolv-retry infinite
nobind
persist-key
persist-tun
remote-cert-tls server
auth SHA512
cipher AES-256-CBC
setenv opt block-outside-dns
key-direction 1
#redirect-gateway def1
pull-filter ignore redirect-gateway
# add a white VPN server IP address to the OpenVPN client config
route IP 255.255.255.255 net_gateway
#ip route add table obhod default dev tun0
verb 3
So, OpenVPN and stunnel are installed and configured on the server and client. A secure connection has been established. Further we will consider each option in turn.
Option 1: VPN client on the user's machine
The first step is to create a list of IP addresses, which we will use next:
Code:
sudo ipset -N vpn iphash
Let's add one address for tests to it:
Code:
sudo ipset -A vpn 8.8.8.8
Now we need to mark packets with destination addresses that match the list.
Code:
sudo iptables - OUTOUT -t mangle -m set --match-set vpn dst -j MARK --set-mark (1 или 0x1)
As a result of our actions, iptables, if there is a match in the destination address and the address from the IPSet list, will start marking such traffic. Next, we will create a new routing table, for this we will write it in a file /etc/iproute2/rt_tableswith a priority higher than default (253):
Code:
252 vpn
Now we need to create a rule and a route for tagged packets.
Code:
sudo ip rule add table vpn prio 1000 fwmark (1 или 0x1)
sudo ip route add table vpn dev tun0 default
Do not forget to enable it rp_filterto ensure asymmetric routing and allow packet forwarding between interfaces, in case someone needs to provide access to closed resources on the local network.
Code:
sudo sysctl net.ipv4.tcp_fwmark_accept=1
sudo sysctl net.ipv4.conf.all.rp_filter=2
sudo net.ipv4.ip_forward=1
Now let's enable NAT for traffic that is routed to the VPN.
Code:
sudo iptables -t nat -I POSTROUTING -o tun0 -j MASQUERADE
If you need to provide access from the local network through your VPN, the iptables rule needs to be slightly tweaked, namely moved to another chain.
Code:
sudo iptables -I PREROUTING -t mangle -m set --match-set tovpn dst -j MARK --set-mark 0x1
As a result, we get that all addresses from the IPSet VPN list go through a secure connection, the rest are forwarded through the default route.
Option 2: VPN client on router
Now let's consider the case when the VPN connection is not performed by a client machine, but by a certain node in the network and everything is configured on it in the same way as in the example above. Then we need to either specify this node as the default gateway, or install IPSet on the client machine and iproute2configure it correctly.
In this case, the local IPSets will work iproute2and send the packet to the intermediate server router, and the latter will check the destination address with its IPSet list and, if it finds a match, will send the packet to the VPN, if it does not find a match, it will forward it to the default gateway.
The disadvantage of this technique is that you have to enter the IP addresses twice in the lists. First, on the router server, and the second time, into its local IPSet list. But it allows for more flexible routing use.
It is also worth noting another option, which was not mentioned above. You can install the VPN client and stunnel on the server router, and then remove the line from the config pull-filter ignore redirect-gateway. Then all received traffic will be wrapped in the VPN tunnel. All that remains is to conduct IPSet locally to direct the required traffic through different gateways.
The IPSet lists are very useful when you need to quickly allow or deny incoming or outgoing connections to the server. For example, many people implement geographic filtering: it is easier to add everything to the lists once and deny all incoming traffic from anywhere except this list with one rule.
For example:
Code:
sudo iptables -A INPUT -m set --match-set !RU src -j DROP
This rule will block all incoming connections, except for addresses from the list ipset RU.
IPSet, as you can see, is a handy tool that greatly reduces the iptables rule table. In particular, it is good to combine server networks, addresses of administrators, other users, and so on into separate lists. And then at once to deny access to one and to allow others.
Automation after crashes / reboots
In order not to lose all the parameters we have configured, add periodic saving of IPSet and iptables rules to crontab.
Code:
sudo crontab -e
# Every first minute of every hour, that is, once an hour
1 * * * * /bin/bash root ipset save > /opt/ipset.rules
1 * * * * iptables-save root > /opt/firewall.rules
And /etc/rc.localadd recovery:
Code:
ipset restore < /opt/routers/ipset.rules
iptables-restore < /opt/routers/iptables.rules
Now let's write a script that will bypass the rules ipruleand iproute, if something is missing, add the missing one. The script can be made a daemon so that it constantly hangs in memory and monitors the state of the system, or you can run it only at the start of the OS. In general, the second option is quite enough.
You can find the source code of the check script in the archive at the link at the end of the article, and its general meaning is as follows: we check the routing table and, if there is no route, add it. Then we perform the same check with the table rule. Then we look at the iptables rule that marks the packets.
Finally, we will write a script that will allow us to more conveniently add IP addresses to the IPSet lists, so that we do not break into the console every time. This script looks like this:
Code:
#!/bin#!/bin
test = $ (zenity --entry --title = "Add an address to ipset lists" --text = "Enter an ip or domain address")
initip=$(dig +short "$test" -u 1.1.1.1)
if [ ! -n "$initip" ]; then
zenity --password --title = "Enter admin password" | sudo -S ipset -exist -A vpn "$ test"
echo "$test" >> /opt/iptovpn.txt
else
zenity --password --title = "Enter admin password" | sudo -S ipset -exist -A vpn "$ initip"
echo "$initip" >> /opt/iptovpn.txt
be
sudo ipset save > /opt/ipset.rules
At launch, we enter the domain name or immediately the IP address. The script will digest both, and then push the IP into the IPSet list.
Also in the archive you will find the add_in_file script, which reads the file with the list of domains, gets the IP and adds it to the IPSet lists.
Conclusion
So what did we do? Here's what. We have raised an encrypted, "white" HTTPS channel from the point of view of the provider. Inside it runs our OpenVPN. The client wraps what we need there, and does not send what is not needed. In principle, you can write a couple more iptables rules, which indicate who is allowed to go to addresses from a given list and who is not. You can specify the sources of requests for greater security and so that, God forbid, an inquisitive user does not get in there. This is implemented using a second IPSet list, where you have to drive in the trusted hosts that will use the access.
If you want, all this not too fancy architecture can be deployed on a Raspberry Pi with Linux and lugged around as a small router. He will provide you with a reliable, secure and encrypted connection anywhere that no Comrade Major can reach.
- A bit of theory
- What is what
- Briefly about two options
- Implementation
- Option 1: VPN client on the user's machine
- Option 2: VPN client on router
- Automation after crashes / reboots
- Conclusion
Linux provides a huge set of routing functions and configuration tools. Experienced sysadmins know this and use the Linux arsenal to its fullest. But many, even advanced users, have no idea how much convenience all these wonderful features can bring. Today we will create routing tables and describe the rules for traversing them, as well as automate the administration of these tables. So, our creative plans:
- let's decide what we need: install the necessary packages and figure out why they are needed;
- we will study the general principle of the bundle operation;
- set up a secure VPN channel using OpenVPN + stunnel;
- We will compose lists of addresses and describe the areas of their application;
- create a script to quickly add a domain or IP address to the IPSet lists with addition to the routing table and inclusion in the redirection rules;
- we use SSH to provide a secure channel of communication to these internet of yours.
What do we need to make everything work, and preferably comfortable? Of course, iptables, where can we go without it. Also iproute2, it will allow us to create a bunch of tables. IPSet will be required in order not to fence the garden with many iptables rules.
What is what
- iptables is a command line utility. A basic firewall management tool for Linux kernels.
- iproute2 is a set of utilities for managing the parameters of network devices in the Linux kernel.
- IPSet is a tool for working with lists of IP addresses and network ports in a network filter. Generates a list in a special format for transmission to the firewall.
- stunnel is a tool for organizing encrypted connections for clients or servers that do not support TLS or SSL. Stunnel intercepts unencrypted data that was supposed to be sent to the network and encrypts it. The program works on both Unix systems and Windows. Uses OpenSSL as encryption to implement the basic TLS and SSL protocol.
- OpenVPN is a VPN server with OpenSSL encryption support. Client parts are available on almost all platforms. Able to work through proxies such as Socks, HTTP, NAT and network filters.
Briefly about two options
The client and server will establish an encrypted communication channel on port 443 (stunnel) and transmit inside OpenVPN on port 995. From the outside, it should look like normal HTTPS.
And here you can implement two connection schemes.
Option 1. On the client machine, iptables will mark packets with a certain flag if the addresses in the packets and IPSet lists match, and send them for routing.
Further, the packets marked with the flag will be sent according to the previously created routing table, the rest will follow the default route. With the help of the console or a small script, we will correct this list of addresses. There may be several lists depending on the situation.
Option 2. A host on the local network (virtual machine, Raspberry Pi or some other device) will act as a client. It will be listed as the default gateway on computers that need to access resources via VPN. Upon receiving a request for an IP address from the list, the gateway will enable NAT and send such traffic to the VPN. The rest of the traffic will be routed to the default gateway without NAT.
For Linux systems, we can leave the default gateway and install IPSet and iproute2, and then configure them similarly to the settings of the "intermediate" host router. In this case, at the client level, traffic will be selected according to the same IPSet list. That is, what is on the list will be sent to the intermediate host router and then to the VPN. The rest will be routed by default.
Implementation
Suppose that somewhere far away in the cloud we already have a VPS server running Ubuntu or Debian. In other distributions, the differences will most likely only be in the installation of the necessary packages. This host in our configuration will be used as the VPN server. There are a lot of recommendations on which VPS to use on the Internet - for different budgets, with different configurations and conditions.
Install OpenVPN, stunnel, git on the server:
Code:
sudo apt install openvpn stunnel4 git
Next, we will use the ready-made script to configure the OpenVPN server. You can, of course, configure everything manually, and I advise you to do just that. Information on how to properly configure OpenVPN is easy to find on the Web. But if you need to get the result very quickly and you are too lazy to mess around, then here is the solution.
Code:
git clone https://github.com/Nyr/openvpn-install.git
cd openvpn-install
sudo ./openvpn-install.sh
We get the installation script and run it. It will help us create user keys and certificates, if necessary, change the network addressing. Everything is very accessible and understandable. At the output, for each user, we will receive one file, inside which there will be a configuration key and certificates (server and user). We transfer this file to the client.
Open the OpenVPN server configuration file and edit it. We select the required port - the one that will be free on the server for connection. It is better to use a more or less inconspicuous port (I chose 995, usually mail ports are left open) so that the client can be guaranteed to connect to the VPN server.
This is how our config will look like:
Code:
port 443
proto tcp
dev tun0
sndbuf 0
rcvbuf 0
ca ca.crt
cert server.crt
key server.key
dh dh.pem
auth SHA512
tls-auth ta.key 0
topology subnet
server 10.8.0.0 255.255.255.0
ifconfig-pool-persist ipp.txt
push "redirect-gateway def1 bypass-dhcp"
push "dhcp-option DNS 208.67.222.222"
push "dhcp-option DNS 208.67.220.220"
push "route 10.8.0.0 255.255.255.0"
route 192.168.31.0 255.255.255.0
keepalive 10 120
cipher AES-256-CBC
user nobody
group nogroup
persist-key
persist-tun
status openvpn-status.log
verb 3
crl-verify crl.pem
The next step is to set up stunnel:
Code:
sudo nano /etc/stunnel/stunnel.conf
output = /var/log/stunnel.log
[openvpn]
client = no
accept = 443
connect = localhost:443
cert = /etc/stunnel/stunnelsrv.pems
Everything is simple here:
- describe where the logs will be saved;
- we indicate the name of the service in any form;
- choose the mode in which the device works: server or client. In this case, client = noindicates server mode;
- accept = 443 indicates the port to which we will connect from the outside (the 443rd port was chosen for a reason - in 99% of cases it is always open, it is easier to pretend to be regular HTTPS, and we will not be noticed even with DPI).
Code:
sudo openssl req -nodes -new -days 365 -newkey rsa:1024 -x509 -keyout stunnelsrv.pem -out stunnel.pem
At this stage, it is important to remember that on the server side we have OpenVPN running on port 995, proto tcp, dev tun0 and stunnel on port 443. All other settings are standard.
Moving on to the client:
Code:
sudo apt install iproute2 ipset stunnel4 git openvpn
We've installed everything we need, now we configure stunnel on the client side:
Code:
output = /var/log/stunnel.log
[openvpn]
client = yes
accept = 127.0.0.1:995
connect = IP:443
cert = /etc/stunnel/stunnel.pem
Here we indicate where to output the logs, client mode is enabled. With the help of the directive, acceptwe say to which address and port to send the connection and where, in fact, to connect to the stunnel client (option connect). Well, what certificate to use.
We transfer the OpenVPN client file from the server. We check the settings: port 995 is used, proto tcp, dev tun0. A very important point: we need to declare a route to our VPS through the default gateway. Otherwise, it turns out that stunnel will establish a connection, then the VPN will connect and try to redirect everything to the tunnel, but stunnel will no longer be able to work, since the host and port will be inaccessible. We also disable the redirection of all traffic to the VPN, check the connection address and port. The VPN server address in the config will be 127.0.0.1 - that is localhost. It is assumed that stunnel has already forwarded the correct client port. You should get something like the following:
Code:
client
dev tun
proto tcp
sndbuf 0
rcvbuf 0
remote 127.0.0.1 995
resolv-retry infinite
nobind
persist-key
persist-tun
remote-cert-tls server
auth SHA512
cipher AES-256-CBC
setenv opt block-outside-dns
key-direction 1
#redirect-gateway def1
pull-filter ignore redirect-gateway
# add a white VPN server IP address to the OpenVPN client config
route IP 255.255.255.255 net_gateway
#ip route add table obhod default dev tun0
verb 3
So, OpenVPN and stunnel are installed and configured on the server and client. A secure connection has been established. Further we will consider each option in turn.
Option 1: VPN client on the user's machine
The first step is to create a list of IP addresses, which we will use next:
Code:
sudo ipset -N vpn iphash
Let's add one address for tests to it:
Code:
sudo ipset -A vpn 8.8.8.8
Now we need to mark packets with destination addresses that match the list.
Code:
sudo iptables - OUTOUT -t mangle -m set --match-set vpn dst -j MARK --set-mark (1 или 0x1)
As a result of our actions, iptables, if there is a match in the destination address and the address from the IPSet list, will start marking such traffic. Next, we will create a new routing table, for this we will write it in a file /etc/iproute2/rt_tableswith a priority higher than default (253):
Code:
252 vpn
Now we need to create a rule and a route for tagged packets.
Code:
sudo ip rule add table vpn prio 1000 fwmark (1 или 0x1)
sudo ip route add table vpn dev tun0 default
Do not forget to enable it rp_filterto ensure asymmetric routing and allow packet forwarding between interfaces, in case someone needs to provide access to closed resources on the local network.
Code:
sudo sysctl net.ipv4.tcp_fwmark_accept=1
sudo sysctl net.ipv4.conf.all.rp_filter=2
sudo net.ipv4.ip_forward=1
Now let's enable NAT for traffic that is routed to the VPN.
Code:
sudo iptables -t nat -I POSTROUTING -o tun0 -j MASQUERADE
If you need to provide access from the local network through your VPN, the iptables rule needs to be slightly tweaked, namely moved to another chain.
Code:
sudo iptables -I PREROUTING -t mangle -m set --match-set tovpn dst -j MARK --set-mark 0x1
As a result, we get that all addresses from the IPSet VPN list go through a secure connection, the rest are forwarded through the default route.
Option 2: VPN client on router
Now let's consider the case when the VPN connection is not performed by a client machine, but by a certain node in the network and everything is configured on it in the same way as in the example above. Then we need to either specify this node as the default gateway, or install IPSet on the client machine and iproute2configure it correctly.
In this case, the local IPSets will work iproute2and send the packet to the intermediate server router, and the latter will check the destination address with its IPSet list and, if it finds a match, will send the packet to the VPN, if it does not find a match, it will forward it to the default gateway.
The disadvantage of this technique is that you have to enter the IP addresses twice in the lists. First, on the router server, and the second time, into its local IPSet list. But it allows for more flexible routing use.
It is also worth noting another option, which was not mentioned above. You can install the VPN client and stunnel on the server router, and then remove the line from the config pull-filter ignore redirect-gateway. Then all received traffic will be wrapped in the VPN tunnel. All that remains is to conduct IPSet locally to direct the required traffic through different gateways.
The IPSet lists are very useful when you need to quickly allow or deny incoming or outgoing connections to the server. For example, many people implement geographic filtering: it is easier to add everything to the lists once and deny all incoming traffic from anywhere except this list with one rule.
For example:
Code:
sudo iptables -A INPUT -m set --match-set !RU src -j DROP
This rule will block all incoming connections, except for addresses from the list ipset RU.
IPSet, as you can see, is a handy tool that greatly reduces the iptables rule table. In particular, it is good to combine server networks, addresses of administrators, other users, and so on into separate lists. And then at once to deny access to one and to allow others.
Automation after crashes / reboots
In order not to lose all the parameters we have configured, add periodic saving of IPSet and iptables rules to crontab.
Code:
sudo crontab -e
# Every first minute of every hour, that is, once an hour
1 * * * * /bin/bash root ipset save > /opt/ipset.rules
1 * * * * iptables-save root > /opt/firewall.rules
And /etc/rc.localadd recovery:
Code:
ipset restore < /opt/routers/ipset.rules
iptables-restore < /opt/routers/iptables.rules
Now let's write a script that will bypass the rules ipruleand iproute, if something is missing, add the missing one. The script can be made a daemon so that it constantly hangs in memory and monitors the state of the system, or you can run it only at the start of the OS. In general, the second option is quite enough.
You can find the source code of the check script in the archive at the link at the end of the article, and its general meaning is as follows: we check the routing table and, if there is no route, add it. Then we perform the same check with the table rule. Then we look at the iptables rule that marks the packets.
Finally, we will write a script that will allow us to more conveniently add IP addresses to the IPSet lists, so that we do not break into the console every time. This script looks like this:
Code:
#!/bin#!/bin
test = $ (zenity --entry --title = "Add an address to ipset lists" --text = "Enter an ip or domain address")
initip=$(dig +short "$test" -u 1.1.1.1)
if [ ! -n "$initip" ]; then
zenity --password --title = "Enter admin password" | sudo -S ipset -exist -A vpn "$ test"
echo "$test" >> /opt/iptovpn.txt
else
zenity --password --title = "Enter admin password" | sudo -S ipset -exist -A vpn "$ initip"
echo "$initip" >> /opt/iptovpn.txt
be
sudo ipset save > /opt/ipset.rules
At launch, we enter the domain name or immediately the IP address. The script will digest both, and then push the IP into the IPSet list.
Also in the archive you will find the add_in_file script, which reads the file with the list of domains, gets the IP and adds it to the IPSet lists.
Conclusion
So what did we do? Here's what. We have raised an encrypted, "white" HTTPS channel from the point of view of the provider. Inside it runs our OpenVPN. The client wraps what we need there, and does not send what is not needed. In principle, you can write a couple more iptables rules, which indicate who is allowed to go to addresses from a given list and who is not. You can specify the sources of requests for greater security and so that, God forbid, an inquisitive user does not get in there. This is implemented using a second IPSet list, where you have to drive in the trusted hosts that will use the access.
If you want, all this not too fancy architecture can be deployed on a Raspberry Pi with Linux and lugged around as a small router. He will provide you with a reliable, secure and encrypted connection anywhere that no Comrade Major can reach.