OpenVPN / openvpn3

OpenVPN 3 is a C++ class library that implements the functionality of an OpenVPN client, and is protocol-compatible with the OpenVPN 2.x branch.
https://openvpn.net
Other
977 stars 386 forks source link

A way to use openvpn3 as a true library #94

Closed lattice0 closed 4 years ago

lattice0 commented 4 years ago

Currently, OpenVPN3 is simply a library that sets up TUN interfaces on systems like Windows, Linux, Android, etc. However, on Android (and I believe iOS), it's not possible to create two different simultaneous VPN connections. If I want to design an app that does simultaneous VPN connections, it's impossible.

Shouldn't it be good to provide an interface for using OpenVPN3 as a C++ library that can send TCP sockets directly without any system interaction (except of course for socket creation)?

I'm actually trying to do that but I don't have the expertise and might atually end up creating something insecure. I migth try to attract some reviewers but wouldn't it be good for it to be officially supported?

dsommers commented 4 years ago

AFAIK, the limitation on not being able to run more than a single VPN connection is a restriction imposed by Android, not the OpenVPN 3 Core library.

If you develop an application with embedded VPN connectivity only for that app, this should already be doable if you create your own TunBuilder class and instead of creating a VPN/tun interface via the OS, you pass a file descriptor managed by your app instead. Then all the network traffic going via the VPN connection will go directly to your own app instead of going via the Android OS. But your app will need to parse and decode IP packets, as well as encode proper IP packets when sending data via the VPN connection.

So from my perspective, this feature already exists, which is why I'm closing this issue now.

lattice0 commented 4 years ago

By examining ovpncli.hpp, which is not from the ovpn3 library, I found

class OpenVPNClient : public TunBuilderBase,            // expose tun builder virtual methods
              public LogReceiver,               // log message notification
              public ExternalTun::Factory,      // low-level tun override
              public ExternalTransport::Factory,// low-level transport override
              private ExternalPKIBase
    {

and found a line in the corresponding cpp file where it does

`cc.builder = this;`

which means it uses OpenVPNClient as the TunBuilderBase when creating a ClientConnect

By looking at TunBuilderBase I see no options to pass a file descriptor. It looks like it's a class mainly for setting up tun related things and interacting with the system.

Did you mean I can do everything without modifying the library at all or do I need to do it?

Also, are you sure there's no way of doing this in the TCP/UDP level? If openvpn3 has support for sending HTTP how does it not have support for TCP?

If I were to guess, I'd say ExternalTransport::Factory in OpenVPNClient was a way to pass a custom TCP transporter, which I could feed to my app to send data. It even has classes for sending a buffer.

dsommers commented 4 years ago

You are looking in the right places, but you need to go deeper into the Core library and look at the establish() methods found in the tunsetup.hpp files. This is where the tun interface is created. And you can extend the library with an implementation suiting your own need. There already exists two in the Core library, openvpn/tun/linux/client/tunsetup.hpp which creates the tun device on Linux and openvpn/tun/mac/client/tunsetup.hpp which does the same with a utun device on macOS. Windows is done differently, as well as the openvpn3-linux, where this is split up into two parts, using TunBuilder and TunBuilderCapture classes to pass the device creation responsibility to another process.

I don't know much about the Windows solution, but in openvpn3-linux the tun_builder_establish() call done in the openvpn3-service-client passes the needed data over D-Bus to the netcfg service which creates and prepares the TunBuilderCapture class which does the proper things, and it ends up returning the file descriptor (the ret variable) to the tun device back to the client. This allows the client to run completely unprivileged while the netcfg service only need privileges to do network configuration change. This might a bit more extensive than what you need, but it is possible to reduce this to a simpler implementation.

Arguments you need to pass to this method typically goes via a TunBuilderSetup::Config. You can also extend this class and cast it accordingly inside establish() to grab the additional fields you need.

One more detail, the tun_builder_establish() method is a callback method which the Core library calls to the implementation responsible for creating the and establishing the TUN device. In your case this would be a "virtual" tun device, not exposed to the OS.

lattice0 commented 4 years ago

Thank you so much!!!!

It looks like you're instructing me on how to create my own "dummy" TUN interface through a file descriptor and then write IP packets to this TUN interface as if it were a socket. This approach could work, but shouldn't it be better to do this:

Instead of sending to a dummy TUN for OpenVPN to read from this TUN and then send the encrypted packet to the server, to send to the server directly without the TUN in the middle?

I think the OpenVPN code that reads from TUN and sends to the server can also be used. This would be more like the funcionatiliy of a library and less dependent on system file descriptors.

You also talked about ways of opening tun devices on various platforms. Do you mean I'd have to open my fake dummy differently on each platform? As I understand, it's a fake dummy, shuldn't it be the same for all platforms?

dsommers commented 4 years ago

I might cause some confusion in my previous comment, and I apologize for that. But the OpenVPN 3 Core library code is written in a very modular way, building on all the advantages C++ Object Oriented Programming provides. And some of the implementation details might even use these possibilities a bit different from what others would do.

So what I do propose above is that you write your own TunBuilder class, building on ClientAPI::OpenVPNClient, where you implement an override the tun_builder_establish() method. In openvpn3-linux, this is "hidden" in line 61 in src/client/core-client-netcfg.hpp and you see the implementation of the methods we need in openvpn3-linux in src/client/core-client-netcfg.hpp. Your implementation of tun_builder_establish() will need to return the file descriptor the Core library will use for reading (which will be sent encrypted to the remote OpenVPN server) and writing the unencrypted traffic (data receive from the remote OpenVPN server and was decrypted). The data you receive on this interface will typically be standard IP packets (as only tun mode is supported).

Which of the other tun_builder_*() methods you need to implement is hard for me to know or guess. If needed to be implemented while you won't need it, then it be quite simple dummies just returning whatever is expected to be returned on success.

You would not need to have platform specific implementations unless the approach you use to retrieve the file descriptor to use is different between platforms. Opening and configuring a normal tun device is naturally different between various platforms, hence the different approaches both in Core library and the variant used in openvpn3-linux. Since you don't need to care about any OS tun device specific, your implementation has the potential to be simpler.

lattice0 commented 4 years ago

Thanks, I think I completely understand how to build one and I'm already working on it and gonna make it available in my github.

However, there's a last problem that I cannot understand. What type of socket am I supposed to pass? In the current model, it passes a file descriptor to an open file which is /dev/tun. How can OpenVPN write and receive from this file? If OpenVPN sends a packet to /dev/tun then wouldn't this packet be read by OpenVPN again?

Do you understand the problem? How can OpenVPN both write and read from the same file? If I write something then I read the same something. Shouldn't there be 2 sockets, one for receiving packets and one for sending?

schwabe commented 4 years ago

/dev/tun is not a file. It is a device. Think of it more like a pipe with one end is what openvpn read/write and the other is the network. A normal is similar, you also write and read to the same socket/file descriptor.

Creating a pipe with pipe() should work with interfacing with openvpn.

lattice0 commented 4 years ago

@schwabe I've just read about pipe and the pipe system call gives back two file descriptors, one for reading to and one for writing to, but the TunSetupLinux requires just one file descriptor, for which I assume it both reads and writes to. So how can pipe work?

schwabe commented 4 years ago

You wanted to implement a dummy tun interface. And one side you give to openvpn3 and the other side you have to implement all the magic of ip processing/etc what @dsommers wrote in his 2nd post.

lattice0 commented 4 years ago

@schwabe yes, but if I give one side to openvpn3, then openvpn3 can only write or receive from this pipe (depending on which side I give). So, if I give the writing side to openvpn3, how is going openvpn3 be able to read packets? If I give the reading side for my app to read IP packets, then how my app can write IP packets?

schwabe commented 4 years ago

I thought pipe was bidirectional but I think it is not. You might to implement your own read/write functions then or find something that is bidirectional.

lattice0 commented 4 years ago

Thank you @schwabe and @dsommers. By using a socketpair I've been able to create a bidirectional socket:

int socket_vector[2];
socketpair(AF_UNIX, SOCK_STREAM, 0, socket_vector);

at least for linux.

I've been crafting packets with libtins:

        Tins::IP pkt = Tins::IP("8.8.8.8") /
                Tins::TCP(80) /
                Tins::RawPDU("I'm a payload!");
        auto m = pkt.serialize();
    uint8_t* message = m.data();
    int length = m.size();
        write(socket_vector[1], message, length);

I've put my client to send this message every 1 second and by doing tcpdump on eth0 in the server I can see an encrypted packet with sligthly larger size arriving every 1 second, so it's arriving!!!! The problem is that tcpdump on tun0 on the server gives nothing. Somehow the packets arrive but are not sent to tun0. Maybe because the packet is malformed? Is there something I'm missing that makes openvpn3 server reject the packet? Maybe it has something to do with the IP packet's source address. I tried sending a packet with the source IP being "0.0.0.0" but it has no effect.