Blame
| 9399f3 | Samuli Seppänen | 2025-02-11 11:49:52 | 1 | # OpenVPN as a Forking TCP Server Which Can Service Multiple Clients Over a Single TCP Port |
| 2 | ||||
| 3 | **WARNING: this article is very outdated and refers to obsolete version of OpenVPN. This article is kept here purely for historical reference.** |
|||
| 4 | ||||
| 5 | See the OpenVPN 2.0 release notes. |
|||
| 6 | ||||
| 7 | Also: |
|||
| 8 | ||||
| 9 | Here at work, we wanted to implement a VPN solution for Roadwarriors who have to connect to the main office. OpenVPN looked like a nice solution, as it also works with Windows and can tunnel through an https proxy. |
|||
| 10 | ||||
| 11 | One problem though, was the inability to use a constant port number on the server side. |
|||
| 12 | ||||
| 13 | The `--inetd` option seemed very promising, but the man-page explicitly states that this option cannot be used for multiple connections, although there seemed to be no reason. (The only reason I found mentioned was that there was no config file templating mechanism, which obviously isn't really a problem). |
|||
| 14 | ||||
| 15 | A test later I found, that the code was written for inetd with "wait=yes" in mind, but that was quickly changed (see attached patch). |
|||
| 16 | ||||
| 17 | The attached patch (merged with OpenVPN as of 1.6-beta5) adds a new option `--inetd nowait` which makes OpenVPN work with a connected socket on stdin, like (x)inetd does with wait=no, or even netcat with the -e option. |
|||
| 18 | ||||
| 19 | Of course, this still runs one OpenVPN process per client. You still need one tap device per client, and configure bridging between them. |
|||
| 20 | ||||
| 21 | This also only works with SSL/TLS and tap devices, because of the single config file shared between all server processes. |
|||
| 22 | ||||
| 23 | The server config file in our case is easy: |
|||
| 24 | ``` |
|||
| 25 | # OpenVPN multiple-client-server |
|||
| 26 | inetd nowait |
|||
| 27 | proto tcp-server |
|||
| 28 | ||||
| 29 | # 10.254.0.1 is our local VPN endpoint (office). |
|||
| 30 | dev tap |
|||
| 31 | ifconfig 10.254.0.1 255.255.255.0 |
|||
| 32 | ||||
| 33 | # Our up script will establish routes once the VPN is alive. |
|||
| 34 | ifconfig-noexec |
|||
| 35 | up /etc/openvpn/vpn-server.up |
|||
| 36 | ||||
| 37 | # We are SSL/TLS Server |
|||
| 38 | tls-server |
|||
| 39 | dh /etc/openvpn/dh2048.pem |
|||
| 40 | ca /etc/openvpn/my-ca.crt |
|||
| 41 | crl-verify /etc/openvpn/my-ca.crl |
|||
| 42 | cert /etc/openvpn/server.crt |
|||
| 43 | key /etc/openvpn/server.key |
|||
| 44 | ``` |
|||
| 45 | ||||
| 46 | The only magic thing is the `ifconfig-noexec`. Of course, we can't `ifconfig` each of the tap interfaces with the same IP address. But we don't need to, as we are using bridging. |
|||
| 47 | ||||
| 48 | My Bridging setup script (run once at bootup): |
|||
| 49 | ``` |
|||
| 50 | # load Bridging-Module |
|||
| 51 | modprobe bridge |
|||
| 52 | ||||
| 53 | # configure bridge |
|||
| 54 | brctl addbr br0 |
|||
| 55 | brctl stp br0 off |
|||
| 56 | brctl setfd br0 0 |
|||
| 57 | ||||
| 58 | # our private vpn network |
|||
| 59 | ifconfig br0 10.254.0.1 netmask 0xffffff00 broadcast 10.254.0.255 |
|||
| 60 | ``` |
|||
| 61 | ||||
| 62 | The per-client setup script (vpn-server.up): |
|||
| 63 | ``` |
|||
| 64 | # add interface to bridge and activate |
|||
| 65 | brctl addif br0 $1 |
|||
| 66 | ifconfig $1 up |
|||
| 67 | ``` |
|||
| 68 | ||||
| 69 | And for completeness sake, here is the xinetd config file for the OpenVPN server: |
|||
| 70 | ``` |
|||
| 71 | service openvpn_1 |
|||
| 72 | { |
|||
| 73 | type = UNLISTED |
|||
| 74 | port = 443 |
|||
| 75 | socket_type = stream |
|||
| 76 | protocol = tcp |
|||
| 77 | wait = no |
|||
| 78 | user = root |
|||
| 79 | server = /usr/sbin/openvpn |
|||
| 80 | server_args = --config /etc/openvpn/vpn-server.conf |
|||
| 81 | disable = no |
|||
| 82 | } |
|||
| 83 | ``` |
|||
| 84 | ||||
| 85 | And that's it. It works for me. If you have any questions or comments, contact me, I'd be happy to hear from you. |
|||
| 86 | ||||
| 87 | CU, |
|||
| 88 | Stefan `Sec` Zehl |
|||
| 89 | ||||
| 90 | The necessary code changes are very small. Only `socket_listen_accept` needs to be changed. The `accept()` and `openvpn_close_socket()` calls need to be removed, because the connection on stdin is already the connection we want. The second thing is, we have to use `getpeername` to find the IP and Port of our remote peer. |
|||
| 91 | ||||
| 92 | All the other changes in the attached patch deal with adding a new command-line option, and propagating that new option down to the correct function. |
