Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use a TUN device and embedded PPP code, instead of using pppd #1048

Open
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

DimitriPapadopoulos
Copy link
Collaborator

@DimitriPapadopoulos DimitriPapadopoulos commented Dec 10, 2022

This patch provided by @rain2fog in #801 (comment) works for me. I get a working VPN tunnel without using pppd. I wouldn't mind help with reviewing.

From #801 (comment):

we simulated the PPP negotiation procedure. There are 3 stages in PPP protocal.

  • The 1th stage is LCP. client will send Magic-Number and MRU request to fortivpn server, then will ack the server request. server will send the Magic-Number and ack the client request.
  • The 2th stage is IPCP. both will will send the IPCP config-request to other. client need to send the IPCP config-request Option (3) with the IP which come from the XML. then both config-request need to be ack.
  • The 3th stage is 0x0021. we can read and write IP packet from tun NIC. then sending data by encapsulating or decapsulating the IP packet with header 0x0021(2 Bytes) + (totolsize(2 Bytes) + 0x5050 + pppsize(2 Bytes)).
    we also need send the LCP echo-request to fortinet VPN to keep the TCP connection wouldn't close.

I get complier warnings on Ubuntu 22.04:

In function ‘conf_option_encode’,
    inlined from ‘conf_request’ at src/io.c:415:2:
src/io.c:337:16: warning: ‘request.tail’ may be used uninitialized [-Wmaybe-uninitialized]
  337 |         optlist->tail->type = type;
      |         ~~~~~~~^~~~~~
src/io.c: In function ‘conf_request’:
src/io.c:410:33: note: ‘request’ declared here
  410 |         struct conf_option_list request;
      |                                 ^~~~~~~
In function ‘conf_option_encode’,
    inlined from ‘ipcp_packet’ at src/io.c:984:4:
src/io.c:337:16: warning: ‘request.tail’ may be used uninitialized [-Wmaybe-uninitialized]
  337 |         optlist->tail->type = type;
      |         ~~~~~~~^~~~~~
src/io.c: In function ‘ipcp_packet’:
src/io.c:981:49: note: ‘request’ declared here
  981 |                         struct conf_option_list request;
      |                                                 ^~~~~~~
In function ‘conf_option_encode’,
    inlined from ‘ipcp_packet’ at src/io.c:1038:4:
src/io.c:337:16: warning: ‘request.tail’ may be used uninitialized [-Wmaybe-uninitialized]
  337 |         optlist->tail->type = type;
      |         ~~~~~~~^~~~~~
src/io.c: In function ‘ipcp_packet’:
src/io.c:1035:49: note: ‘request’ declared here
 1035 |                         struct conf_option_list request;
      |                                                 ^~~~~~~

Initially opened as #818.

Fixes #801.

@DimitriPapadopoulos DimitriPapadopoulos changed the title Use a TUN adevice and embedded PPP code, instead of using pppd Use a TUN device and embedded PPP code, instead of using pppd Dec 10, 2022
src/io.c Fixed Show fixed Hide fixed
src/io.c Dismissed Show dismissed Hide dismissed
@DimitriPapadopoulos
Copy link
Collaborator Author

I get these warnings while setting routes after connection:

WARN:   Could not get route to gateway (Route not found).
WARN:   Protecting tunnel route has failed. But this can be working except for some cases.
WARN:   Adding route table is incomplete. Please check route table.

I get this error when cleaning up because openfortivpn still attempts to terminate `pppd``:

ERROR:  waitpid: No child processes
INFO:   Terminated pppd.

@relaxlex
Copy link

Hi @DimitriPapadopoulos,

thank you for applying the TUN patch from @rain2fog. I have just ran a couple of short tests and this appears to work like a charm. Because I use openfortivpn daily I will use this tun branch build from now on to see how it performs. Because I can create a tun device as a normal user I will be running openfortivpn without sudo.
Only difference I observed between the TUN and pppd version is that the TUN version is seeing a lot of
"Route to gateway exists already." warnings when connecting to the same Forti VPN server.

Cheers!

@relaxlex
Copy link

Hi @DimitriPapadopoulos,

thank you for the update, will test further with this rev. g2dec255 version. First glance TUN tunnel still working and Route to gateway exists already warnings are also still there.

Cheers!

@DimitriPapadopoulos
Copy link
Collaborator Author

Yes, I probably won't have time to look into the warnings any time soon. You are welcome to suggest a pull request to fix them.

src/io.c Fixed Show fixed Hide fixed
@DimitriPapadopoulos
Copy link
Collaborator Author

Fixed compiler warnings, just took a couple assert calls.

@relaxlex
Copy link

Thank you @DimitriPapadopoulos for working on this. When I use the --tun=1 argument a ppp interface is created. I think we also need to adjust the merge_config because after the merge function cfg.tun appears to be 0, which would explain why a ppp interface is created and not a tun interface. (Sorry not a programmer here, can read some code and put in some debug lines)

@sino1641
Copy link

sino1641 commented Mar 3, 2024

Hi,
From @relaxlex it seems the experimental tun version should work.
But I got some complier errors on the latest commit: 5493005

Log
/openfortivpn # git log -1 --format="%h"
5493005
/openfortivpn # make
  CC       src/openfortivpn-config.o
  CC       src/openfortivpn-hdlc.o
  CC       src/openfortivpn-http.o
  CC       src/openfortivpn-io.o
src/io.c: In function 'lcp_option_send':
src/io.c:384:43: error: 'PPP_LCP' undeclared (first use in this function); did you mean 'TUN_PPP_LCP'?
  384 |                         *ppp_type = htons(PPP_LCP);
      |                                           ^~~~~~~
      |                                           TUN_PPP_LCP
src/io.c:384:43: note: each undeclared identifier is reported only once for each function it appears in
src/io.c: In function 'lcp_packet':
src/io.c:457:38: error: 'PPP_CHAP' undeclared (first use in this function)
  457 |                                 case PPP_CHAP: {
      |                                      ^~~~~~~~
src/io.c: In function 'ipcp_add_route':
src/io.c:743:15: warning: implicit declaration of function 'ioctl' [-Wimplicit-function-declaration]
  743 |         ret = ioctl(sd, SIOCADDRT, &rt);
      |               ^~~~~
src/io.c:743:25: error: 'SIOCADDRT' undeclared (first use in this function); did you mean 'BIO_ADDR'?
  743 |         ret = ioctl(sd, SIOCADDRT, &rt);
      |                         ^~~~~~~~~
      |                         BIO_ADDR
src/io.c: In function 'ipcp_option_send':
src/io.c:770:43: error: 'PPP_IPCP' undeclared (first use in this function); did you mean 'TUN_PPP_IPCP'?
  770 |                         *ppp_type = htons(PPP_IPCP);
      |                                           ^~~~~~~~
      |                                           TUN_PPP_IPCP
src/io.c: In function 'pppd_write':
src/io.c:1245:30: error: 'PPP_LCP' undeclared (first use in this function); did you mean 'TUN_PPP_LCP'?
 1245 |                         case PPP_LCP:
      |                              ^~~~~~~
      |                              TUN_PPP_LCP
src/io.c:1248:30: error: 'PPP_IPCP' undeclared (first use in this function); did you mean 'TUN_PPP_IPCP'?
 1248 |                         case PPP_IPCP:
      |                              ^~~~~~~~
      |                              TUN_PPP_IPCP
src/io.c:1251:30: error: 'PPP_IP' undeclared (first use in this function)
 1251 |                         case PPP_IP:
      |                              ^~~~~~
src/io.c:1252:30: error: 'PPP_IPV6' undeclared (first use in this function)
 1252 |                         case PPP_IPV6:
      |                              ^~~~~~~~
make: *** [Makefile:575: src/openfortivpn-io.o] Error 1

@DimitriPapadopoulos
Copy link
Collaborator Author

DimitriPapadopoulos commented Mar 3, 2024

@sino1641 Something went wrong with the build process. Restart building from scratch, from a fresh checkout.

(you haven't shown the whole build process)

@sino1641
Copy link

sino1641 commented Mar 3, 2024

@DimitriPapadopoulos Thanks for pointing it out.
I tried to build it under an alpine container on a Linux host with no ppp kernel.
(But it builds a ppp-based binary from master successfully.)
Entire process:

Log
/ # apk add --no-cache autoconf automake build-base ca-certificates curl git openssl-dev
/ # git clone -b tun https://github.com/adrienverge/openfortivpn.git
/ # cd openfortivpn/
/openfortivpn # ./autogen.sh
+ type autoconf
+ type automake
+ type aclocal
+ aclocal
+ autoconf
+ automake --add-missing
configure.ac:10: installing './compile'
configure.ac:7: installing './install-sh'
configure.ac:7: installing './missing'
Makefile.am: installing './depcomp'
+ echo 'now you can run ./configure && make to build openfortivpn'
now you can run ./configure && make to build openfortivpn
/openfortivpn # ./configure
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a race-free mkdir -p... ./install-sh -c -d
checking for gawk... no
checking for mawk... no
checking for nawk... no
checking for awk... awk
checking whether make sets $(MAKE)... yes
checking whether make supports nested variables... yes
checking for gcc... gcc
checking whether the C compiler works... yes
checking for C compiler default output file name... a.out
checking for suffix of executables... 
checking whether we are cross compiling... no
checking for suffix of object files... o
checking whether the compiler supports GNU C... yes
checking whether gcc accepts -g... yes
checking for gcc option to enable C11 features... none needed
checking whether gcc understands -c and -o together... yes
checking whether make supports the include directive... yes (GNU style)
checking dependency style of gcc... gcc3
checking for a sed that does not truncate output... /bin/sed
checking for stdio.h... yes
checking for stdlib.h... yes
checking for string.h... yes
checking for inttypes.h... yes
checking for stdint.h... yes
checking for strings.h... yes
checking for sys/stat.h... yes
checking for sys/types.h... yes
checking for unistd.h... yes
checking for wchar.h... yes
checking for minix/config.h... no
checking whether it is safe to define __EXTENSIONS__... yes
checking whether _XOPEN_SOURCE should be defined... no
checking for pkg-config... /usr/bin/pkg-config
checking pkg-config is at least version 0.9.0... yes
checking for git... /usr/bin/git
checking whether make supports nested variables... (cached) yes
checking for libssl >= 1.0.2 libcrypto >= 1.0.2... yes
checking for pthread_create in -lpthread... yes
checking for forkpty in -lutil... yes
checking for libsystemd... no
libsystemd not present
checking for arpa/inet.h... yes
checking for fcntl.h... yes
checking for getopt.h... yes
checking for ifaddrs.h... yes
checking for netdb.h... yes
checking for net/if.h... yes
checking for netinet/in.h... yes
checking for netinet/tcp.h... yes
checking for pthread.h... yes
checking for strings.h... (cached) yes
checking for sys/ioctl.h... yes
checking for syslog.h... yes
checking for sys/socket.h... yes
checking for sys/stat.h... (cached) yes
checking for sys/types.h... (cached) yes
checking for sys/wait.h... yes
checking for termios.h... yes
checking for unistd.h... (cached) yes
checking for net/route.h... yes
checking for libutil.h... no
checking for linux/if_ppp.h... no
checking for linux/if_tun.h... no
checking for mach/mach.h... no
checking for pty.h... yes
checking for semaphore.h... yes
checking for util.h... no
checking for net/if_var.h... no
checking for an ANSI C-conforming const... yes
checking for inline... inline
checking for working volatile... yes
checking for off_t... yes
checking for pid_t... yes
checking for size_t... yes
checking for ssize_t... yes
checking for uint16_t... yes
checking for uint32_t... yes
checking for uint8_t... yes
checking for struct termios... yes
checking for access... yes
checking for close... yes
checking for connect... yes
checking for execv... yes
checking for _exit... yes
checking for fcntl... yes
checking for fileno... yes
checking for forkpty... yes
checking for freeaddrinfo... yes
checking for freeifaddrs... yes
checking for gai_strerror... yes
checking for getaddrinfo... yes
checking for geteuid... yes
checking for getifaddrs... yes
checking for getopt_long... yes
checking for htons... yes
checking for inet_addr... yes
checking for inet_ntoa... yes
checking for ioctl... yes
checking for isatty... yes
checking for memmem... yes
checking for ntohs... yes
checking for open... yes
checking for openlog... yes
checking for pclose... yes
checking for popen... yes
checking for pthread_cancel... yes
checking for pthread_cond_init... yes
checking for pthread_cond_signal... yes
checking for pthread_cond_wait... yes
checking for pthread_create... yes
checking for pthread_join... yes
checking for pthread_mutexattr_init... yes
checking for pthread_mutex_destroy... yes
checking for pthread_mutex_init... yes
checking for pthread_mutex_lock... yes
checking for pthread_mutex_unlock... yes
checking for pthread_self... yes
checking for pthread_sigmask... yes
checking for read... yes
checking for select... yes
checking for sem_destroy... yes
checking for sem_init... yes
checking for sem_post... yes
checking for sem_wait... yes
checking for setenv... yes
checking for setsockopt... yes
checking for sigaddset... yes
checking for sigemptyset... yes
checking for sleep... yes
checking for socket... yes
checking for strcasecmp... yes
checking for strdup... yes
checking for strncasecmp... yes
checking for strsignal... yes
checking for strtok_r... yes
checking for syslog... yes
checking for tcgetattr... yes
checking for tcsetattr... yes
checking for usleep... yes
checking for vsyslog... yes
checking for waitpid... yes
checking for write... yes
checking for pthread_mutexattr_setrobust... yes
checking whether rtentry is available and has rt_dst... yes
checking for /proc/net/route... yes
checking for ppp... /usr/sbin/ppp
checking for /usr/sbin/ppp... no
checking for pppd... /usr/sbin/pppd
checking for /usr/sbin/pppd... no
configure: HAVE_USR_SBIN_PPP... 0
configure: HAVE_USR_SBIN_PPPD... 1
configure: LEGACY_PPPD... 0
configure: HAVE_PROC_NET_ROUTE... 1
configure: PPP_PATH... /usr/sbin/pppd
checking for resolvconf... DISABLED
configure: RESOLVCONF_PATH... DISABLED
configure: HAVE_RESOLVCONF... 0
configure: USE_RESOLVCONF... 0
configure: systemdsystemunitdir... 
configure: HAVE_SO_BINDTODEVICE... 1
checking that generated files are newer than configure... done
configure: creating ./config.status
config.status: creating Makefile
config.status: executing depfiles commands
config.status: executing timestamp commands
/openfortivpn # make
  CC       src/openfortivpn-config.o
  CC       src/openfortivpn-hdlc.o
  CC       src/openfortivpn-http.o
  CC       src/openfortivpn-io.o
src/io.c: In function 'lcp_option_send':
src/io.c:384:43: error: 'PPP_LCP' undeclared (first use in this function); did you mean 'TUN_PPP_LCP'?
  384 |                         *ppp_type = htons(PPP_LCP);
      |                                           ^~~~~~~
      |                                           TUN_PPP_LCP
src/io.c:384:43: note: each undeclared identifier is reported only once for each function it appears in
src/io.c: In function 'lcp_packet':
src/io.c:457:38: error: 'PPP_CHAP' undeclared (first use in this function)
  457 |                                 case PPP_CHAP: {
      |                                      ^~~~~~~~
src/io.c: In function 'ipcp_add_route':
src/io.c:743:15: warning: implicit declaration of function 'ioctl' [-Wimplicit-function-declaration]
  743 |         ret = ioctl(sd, SIOCADDRT, &rt);
      |               ^~~~~
src/io.c:743:25: error: 'SIOCADDRT' undeclared (first use in this function); did you mean 'BIO_ADDR'?
  743 |         ret = ioctl(sd, SIOCADDRT, &rt);
      |                         ^~~~~~~~~
      |                         BIO_ADDR
src/io.c: In function 'ipcp_option_send':
src/io.c:770:43: error: 'PPP_IPCP' undeclared (first use in this function); did you mean 'TUN_PPP_IPCP'?
  770 |                         *ppp_type = htons(PPP_IPCP);
      |                                           ^~~~~~~~
      |                                           TUN_PPP_IPCP
src/io.c: In function 'pppd_write':
src/io.c:1245:30: error: 'PPP_LCP' undeclared (first use in this function); did you mean 'TUN_PPP_LCP'?
 1245 |                         case PPP_LCP:
      |                              ^~~~~~~
      |                              TUN_PPP_LCP
src/io.c:1248:30: error: 'PPP_IPCP' undeclared (first use in this function); did you mean 'TUN_PPP_IPCP'?
 1248 |                         case PPP_IPCP:
      |                              ^~~~~~~~
      |                              TUN_PPP_IPCP
src/io.c:1251:30: error: 'PPP_IP' undeclared (first use in this function)
 1251 |                         case PPP_IP:
      |                              ^~~~~~
src/io.c:1252:30: error: 'PPP_IPV6' undeclared (first use in this function)
 1252 |                         case PPP_IPV6:
      |                              ^~~~~~~~
make: *** [Makefile:575: src/openfortivpn-io.o] Error 1

@DimitriPapadopoulos
Copy link
Collaborator Author

DimitriPapadopoulos commented Mar 3, 2024

There may be a mix-up between <linux/if_ppp.h> and <net/if_ppp.h>.

The log reads:

checking for linux/if_ppp.h... no
checking for linux/if_tun.h... no

so HAVE_LINUX_IF_PPP_H is undefined.

But then we include <net/if_ppp.h>, not <linux/if_ppp.h>:

openfortivpn/src/io.c

Lines 38 to 40 in 5493005

#if HAVE_LINUX_IF_PPP_H
#include <net/if_ppp.h>
#endif

This raises the following questions:

  1. While <linux/if_ppp.h> is not available on your server, is <net/if_ppp.h> available?
  2. If <net/if_ppp.h> is available, does it define the missing PPP_* macros, especially without <linux/if_ppp.h> available?
  3. Shouldn't we define PPP_* macros ourselves instead of relying on <net/if_ppp.h>/<linux/if_ppp.h>?

@DimitriPapadopoulos
Copy link
Collaborator Author

DimitriPapadopoulos commented Mar 3, 2024

  • I have fixed src/io.c to include <sys/ioctl.h>, which should fix part of the compiler errors.
  • I have fixed configure.ac to test for <net/if_ppp.h> instead of <linux/if_ppp.h>.
  • I have modified src/io.c to define the missing PPP_* macros when <net/if_ppp.h> is missing. Is this last change useful to you? Said otherwise, is <net/if_ppp.h> missing from your machine?

@sino1641
Copy link

sino1641 commented Mar 3, 2024

Sincerely thanks Dimitri!
It seems I missed package linux-headers which contains linux/if_ppp.h and linux/if_tun.h.
But then there is another error saying:

src/io.c:38:10: fatal error: net/if_ppp.h: No such file or directory
   38 | #include <net/if_ppp.h>
      |          ^~~~~~~~~~~~~~
compilation terminated.
make: *** [Makefile:575: src/openfortivpn-io.o] Error 1

Which means yes HAVE_LINUX_IF_PPP_H is working.

Searching Internet, I see that's because some conflicts on musl which Alpine is using so that <net/if_ppp.h> is not found.
(Refer:https://gitlab.alpinelinux.org/alpine/aports/-/issues/9643)

openfortivpn/src/io.c

Lines 44 to 52 in 15ee42a

#if !HAVE_NET_IF_PPP_H
#include <net/if_ppp.h>
#else
#define PPP_IP 0x21 /* Internet Protocol */
#define PPP_IPV6 0x57 /* Internet Protocol Version 6 */
#define PPP_IPCP 0x8021 /* IP Control Protocol */
#define PPP_LCP 0xc021 /* Link Control Protocol */
#define PPP_CHAP 0xc223 /* Cryptographic Handshake Auth. Protocol */
#endif

And for your latest fix, I think this should be a positive status check to define the missing PPP_* macros as behind when <net/if_ppp.h> is missing.

Now it works with all your commits, especially 15ee42a.

@DimitriPapadopoulos
Copy link
Collaborator Author

DimitriPapadopoulos commented Mar 4, 2024

The ! in !HAVE_NET_IF_PPP_H was a temporary test to see what happens when <net/if_ppp.h> is not available.

I understand we need to switch from <net/if_ppp.h> to <linux/if_ppp.h> to support the variety of C system libraries available on Linux systems. I'll change that.

DimitriPapadopoulos and others added 8 commits April 22, 2024 15:00
pppd → tun interface + embedded PPP code
Switch from pppd/ppp to a tun device and internal PPP code.
This commit modifies the `merge_config` function to include the 'tun' value
from the source configuration if it is set. Previously, this value was not
being merged, leading to inconsistencies in the final configuration.

The change checks if 'tun' in the source configuration is different from its
default value before merging it into the destination configuration.

This ensures that the 'tun' setting is correctly propagated when
configurations are merged, allowing for more flexible and accurate VPN setups.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Do you know something about TUN method?
4 participants