Merge changes from topic "pppolac"

* changes:
  mtpd: Use L2TP implementation from mainline kernel
  mtpd: l2tp: Fix endianness issues in log prints
  mtpd: Remove obsolete way of passing args to pppd
diff --git a/l2tp.c b/l2tp.c
index 7dd550a..1d6171e 100644
--- a/l2tp.c
+++ b/l2tp.c
@@ -14,11 +14,15 @@
  * limitations under the License.
  */
 
-/* A simple implementation of L2TP Access Concentrator (RFC 2661) which only
- * creates a single session. The following code only handles control packets.
- * Data packets are handled by PPPoLAC driver which can be found in Android
- * kernel tree. */
+/*
+ * Implementation of L2TP Access Concentrator (RFC 2661). The following code
+ * only handles control packets. Data packets are handled by kernel driver:
+ *  - PX_PROTO_OL2TP (upstream impl.), if it's enabled in kernel
+ *  - or PX_PROTO_OLAC (Android impl.), if upstream implementation is not
+ *    available / not enabled. It will be removed in new Android kernels.
+ */
 
+#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -31,6 +35,7 @@
 #include <arpa/inet.h>
 #include <linux/netdevice.h>
 #include <linux/if_pppox.h>
+#include <linux/types.h>
 #include <openssl/md5.h>
 
 #include "mtpd.h"
@@ -89,11 +94,11 @@
 #define ATTRIBUTE_HEADER_SIZE   6
 #define MAX_ATTRIBUTE_SIZE      1024
 
-static uint16_t local_tunnel;
-static uint16_t local_session;
+static __be16 local_tunnel;
+static __be16 local_session;
 static uint16_t local_sequence;
-static uint16_t remote_tunnel;
-static uint16_t remote_session;
+static __be16 remote_tunnel;
+static __be16 remote_session;
 static uint16_t remote_sequence;
 
 static uint16_t state;
@@ -321,7 +326,8 @@
         local_tunnel = random();
     }
 
-    log_print(DEBUG, "Sending SCCRQ (local_tunnel = %d)", local_tunnel);
+    log_print(DEBUG, "Sending SCCRQ (local_tunnel = %u)",
+              (unsigned)ntohs(local_tunnel));
     state = SCCRQ;
     set_message(0, SCCRQ);
     add_attribute_u16(PROTOCOL_VERSION, htons(0x0100));
@@ -347,10 +353,40 @@
     return TIMEOUT_INTERVAL;
 }
 
-static int create_pppox()
+/**
+ * Check if upstream kernel implementation is enabled.
+ *
+ * @return true if upstream L2TP is enabled in kernel and false otherwise
+ */
+static bool check_ol2tp(void)
 {
-    int pppox = socket(AF_PPPOX, SOCK_DGRAM, PX_PROTO_OLAC);
+    int fd = socket(AF_PPPOX, SOCK_DGRAM, PX_PROTO_OL2TP);
+
+    if (fd < 0) {
+        return false;
+    } else {
+        close(fd);
+        return true;
+    }
+}
+
+/**
+ * Create OLAC session.
+ *
+ * @deprecated It will be removed soon in favor of upstream OL2TP.
+ *
+ * @return PPPoX socket file descriptor
+ */
+static int create_pppox_olac(void)
+{
+    int pppox;
+
+    log_print(WARNING, "Using deprecated OLAC protocol. "
+                       "Its support will be removed soon. "
+                       "Please enable OL2TP support in your kernel");
+
     log_print(INFO, "Creating PPPoX socket");
+    pppox = socket(AF_PPPOX, SOCK_DGRAM, PX_PROTO_OLAC);
 
     if (pppox == -1) {
         log_print(FATAL, "Socket() %s", strerror(errno));
@@ -371,6 +407,69 @@
     return pppox;
 }
 
+/**
+ * Create OL2TP tunnel and session.
+ *
+ * @param[out] tfd Will contain tunnel socket file descriptor
+ * @param[out] sfd Will contain session socket file descriptor
+ */
+static void create_pppox_ol2tp(int *tfd, int *sfd)
+{
+    int tunnel_fd;
+    int session_fd;
+    struct sockaddr_pppol2tp tunnel_sa;
+    struct sockaddr_pppol2tp session_sa;
+
+    log_print(INFO, "Creating PPPoX tunnel socket...");
+    tunnel_fd = socket(AF_PPPOX, SOCK_DGRAM, PX_PROTO_OL2TP);
+    if (tunnel_fd < 0) {
+        log_print(FATAL, "Tunnel socket(): %s", strerror(errno));
+        exit(SYSTEM_ERROR);
+    }
+
+    memset(&tunnel_sa, 0, sizeof(tunnel_sa));
+    tunnel_sa.sa_family = AF_PPPOX;
+    tunnel_sa.sa_protocol = PX_PROTO_OL2TP;
+    tunnel_sa.pppol2tp.fd = the_socket; /* UDP socket */
+    tunnel_sa.pppol2tp.s_tunnel = ntohs(local_tunnel);
+    tunnel_sa.pppol2tp.s_session = 0;   /* special case: mgmt socket */
+    tunnel_sa.pppol2tp.d_tunnel = ntohs(remote_tunnel);
+    tunnel_sa.pppol2tp.d_session = 0;   /* special case: mgmt socket */
+
+    log_print(INFO, "Connecting to tunnel socket...");
+    if (connect(tunnel_fd, (struct sockaddr *)&tunnel_sa,
+                sizeof(tunnel_sa))) {
+        log_print(FATAL, "Tunnel connect(): %s", strerror(errno));
+        exit(SYSTEM_ERROR);
+    }
+
+    log_print(INFO, "Creating PPPoX session socket...");
+    session_fd = socket(AF_PPPOX, SOCK_DGRAM, PX_PROTO_OL2TP);
+    if (session_fd < 0) {
+        log_print(FATAL, "Session socket(): %s", strerror(errno));
+        exit(SYSTEM_ERROR);
+    }
+
+    memset(&session_sa, 0, sizeof(session_sa));
+    session_sa.sa_family = AF_PPPOX;
+    session_sa.sa_protocol = PX_PROTO_OL2TP;
+    session_sa.pppol2tp.fd = the_socket;
+    session_sa.pppol2tp.s_tunnel = ntohs(local_tunnel);
+    session_sa.pppol2tp.s_session = ntohs(local_session);
+    session_sa.pppol2tp.d_tunnel = ntohs(remote_tunnel);
+    session_sa.pppol2tp.d_session = ntohs(remote_session);
+
+    log_print(INFO, "Connecting to session socket...");
+    if (connect(session_fd, (struct sockaddr *)&session_sa,
+                sizeof(session_sa))) {
+        log_print(FATAL, "Session connect(): %s", strerror(errno));
+        exit(SYSTEM_ERROR);
+    }
+
+    *tfd = tunnel_fd;
+    *sfd = session_fd;
+}
+
 static uint8_t *compute_response(uint8_t type, void *challenge, int size)
 {
     static uint8_t response[MD5_DIGEST_LENGTH];
@@ -383,18 +482,18 @@
     return response;
 }
 
-static int verify_challenge()
+static bool verify_challenge()
 {
     if (secret) {
         uint8_t response[MD5_DIGEST_LENGTH];
         if (get_attribute_raw(CHALLENGE_RESPONSE, response, MD5_DIGEST_LENGTH)
                 != MD5_DIGEST_LENGTH) {
-            return 0;
+            return false;
         }
         return !memcmp(compute_response(SCCRP, challenge, CHALLENGE_SIZE),
                 response, MD5_DIGEST_LENGTH);
     }
-    return 1;
+    return true;
 }
 
 static void answer_challenge()
@@ -412,8 +511,8 @@
 static int l2tp_process()
 {
     uint16_t sequence = local_sequence;
-    uint16_t tunnel = 0;
-    uint16_t session = 0;
+    __be16 tunnel = 0;
+    __be16 session = 0;
 
     if (!recv_packet(&session)) {
         return acknowledged ? 0 : TIMEOUT_INTERVAL;
@@ -427,8 +526,8 @@
                 if (get_attribute_u16(ASSIGNED_TUNNEL, &tunnel) && tunnel &&
                         verify_challenge()) {
                     remote_tunnel = tunnel;
-                    log_print(DEBUG, "Received SCCRP (remote_tunnel = %d) -> "
-                            "Sending SCCCN", remote_tunnel);
+                    log_print(DEBUG, "Received SCCRP (remote_tunnel = %u) -> "
+                            "Sending SCCCN", (unsigned)ntohs(remote_tunnel));
                     state = SCCCN;
                     set_message(0, SCCCN);
                     answer_challenge();
@@ -445,8 +544,8 @@
             if (state == ICRQ && session == local_session) {
                 if (get_attribute_u16(ASSIGNED_SESSION, &session) && session) {
                     remote_session = session;
-                    log_print(DEBUG, "Received ICRP (remote_session = %d) -> "
-                            "Sending ICCN", remote_session);
+                    log_print(DEBUG, "Received ICRP (remote_session = %u) -> "
+                            "Sending ICCN", (unsigned)ntohs(remote_session));
                     state = ICCN;
                     set_message(remote_session, ICCN);
                     add_attribute_u32(CONNECT_SPEED, htonl(100000000));
@@ -467,8 +566,8 @@
 
         case CDN:
             if (session && session == local_session) {
-                log_print(DEBUG, "Received CDN (local_session = %d)",
-                        local_session);
+                log_print(DEBUG, "Received CDN (local_session = %u)",
+                        (unsigned)ntohs(local_session));
                 log_print(INFO, "Remote server hung up");
                 return -REMOTE_REQUESTED;
             }
@@ -484,7 +583,8 @@
                     local_session = random();
                 }
                 log_print(DEBUG, "Received %s -> Sending ICRQ (local_session = "
-                        "%d)", messages[incoming.message], local_session);
+                        "%u)", messages[incoming.message],
+                        (unsigned)ntohs(local_session));
                 log_print(INFO, "Tunnel established");
                 state = ICRQ;
                 set_message(0, ICRQ);
@@ -504,7 +604,17 @@
             if (state == ICCN) {
                 log_print(INFO, "Session established");
                 state = ACK;
-                start_pppd(create_pppox());
+
+                if (check_ol2tp()) {
+                    int tunnel_fd, session_fd;
+
+                    create_pppox_ol2tp(&tunnel_fd, &session_fd);
+                    start_pppd_ol2tp(tunnel_fd, session_fd,
+                                     ntohs(remote_tunnel),
+                                     ntohs(remote_session));
+                } else {
+                    start_pppd(create_pppox_olac());
+                }
             }
             return 0;
 
@@ -513,8 +623,9 @@
             /* Since we run pppd as a client, it does not makes sense to
              * accept ICRQ or OCRQ. Always send CDN with a proper error. */
             if (get_attribute_u16(ASSIGNED_SESSION, &session) && session) {
-                log_print(DEBUG, "Received %s (remote_session = %d) -> "
-                        "Sending CDN", messages[incoming.message], session);
+                log_print(DEBUG, "Received %s (remote_session = %u) -> "
+                        "Sending CDN", messages[incoming.message],
+                        (unsigned)ntohs(session));
                 set_message(session, CDN);
                 add_attribute_u32(RESULT_CODE, htonl(0x00020006));
                 add_attribute_u16(ASSIGNED_SESSION, 0);
diff --git a/mtpd.c b/mtpd.c
index ae0a1e6..53ea362 100644
--- a/mtpd.c
+++ b/mtpd.c
@@ -28,6 +28,7 @@
 #include <unistd.h>
 #include <fcntl.h>
 #include <time.h>
+#include <limits.h>
 
 #ifdef ANDROID_CHANGES
 #include <android/log.h>
@@ -37,6 +38,12 @@
 #include "mtpd.h"
 #include "NetdClient.h"
 
+#define ARRAY_SIZE(a)           (sizeof(a) / sizeof(a[0]))
+/* Characters count in string with max value of unsigned type t */
+#define TYPE_STRLEN_U(t)        ((((sizeof(t) * CHAR_BIT) * 1233) >> 12) + 1)
+/* Length of string with max file descriptor value */
+#define FD_MAX_LEN              TYPE_STRLEN_U(int)
+
 int the_socket = -1;
 
 extern struct protocol l2tp;
@@ -322,9 +329,9 @@
 
     if (!pppd_pid) {
         char *args[pppd_argc + 5];
-        char number[12];
+        char number[FD_MAX_LEN + 1];
 
-        sprintf(number, "%d", pppox);
+        snprintf(number, FD_MAX_LEN + 1, "%d", pppox);
         args[0] = "pppd";
         args[1] = "nodetach";
         args[2] = "pppox";
@@ -332,29 +339,82 @@
         memcpy(&args[4], pppd_argv, sizeof(char *) * pppd_argc);
         args[4 + pppd_argc] = NULL;
 
-#ifdef ANDROID_CHANGES
-        {
-            char envargs[65536];
-            char *tail = envargs;
-            int i;
-            /* Hex encode the arguments using [A-P] instead of [0-9A-F]. */
-            for (i = 0; args[i]; ++i) {
-                char *p = args[i];
-                do {
-                    *tail++ = 'A' + ((*p >> 4) & 0x0F);
-                    *tail++ = 'A' + (*p & 0x0F);
-                } while (*p++);
-            }
-            *tail = 0;
-            setenv("envargs", envargs, 1);
-            args[1] = NULL;
-        }
-#endif
         execvp("pppd", args);
         log_print(FATAL, "Exec() %s", strerror(errno));
-        exit(1); /* Pretending a fatal error in pppd. */
+        exit(SYSTEM_ERROR); /* Pretending a fatal error in pppd. */
     }
 
     log_print(INFO, "Pppd started (pid = %d)", pppd_pid);
     close(pppox);
 }
+
+/**
+ * Start pppd daemon with pppol2tp-android plugin.
+ *
+ * @param tunnel_fd Tunnel socket file descriptor
+ * @param session_fd Session socket file descriptor
+ * @param tunnel_id Tunnel ID; must be in host byte order
+ * @param session_id Session ID; must be in host byte order
+ */
+void start_pppd_ol2tp(int tunnel_fd, int session_fd, int tunnel_id,
+                      int session_id)
+{
+    if (pppd_pid) {
+        log_print(WARNING, "Pppd is already started (pid = %d)", pppd_pid);
+        goto ret;
+    }
+
+    log_print(INFO, "Starting pppd (tunnel_fd = %d, session_fd = %d)",
+              tunnel_fd, session_fd);
+
+    pppd_pid = fork();
+    if (pppd_pid < 0) {
+        log_print(FATAL, "Fork() %s", strerror(errno));
+        exit(SYSTEM_ERROR);
+    }
+
+    if (!pppd_pid) {
+        char tunnel_fd_str[FD_MAX_LEN + 1];
+        char session_fd_str[FD_MAX_LEN + 1];
+        char tunnel_id_str[FD_MAX_LEN + 1];
+        char session_id_str[FD_MAX_LEN + 1];
+
+        snprintf(tunnel_fd_str, FD_MAX_LEN + 1, "%d", tunnel_fd);
+        snprintf(session_fd_str, FD_MAX_LEN + 1, "%d", session_fd);
+        snprintf(tunnel_id_str, FD_MAX_LEN + 1, "%d", tunnel_id);
+        snprintf(session_id_str, FD_MAX_LEN + 1, "%d", session_id);
+
+        const char *l2tp_args[] = {
+            "pppd",
+            "nodetach",
+            "plugin",
+            "pppol2tp-android.so",
+            "session_fd",
+            session_fd_str,
+            "tunnel_fd",
+            tunnel_fd_str,
+            "session_id",
+            session_id_str,
+            "tunnel_id",
+            tunnel_id_str,
+        };
+        const size_t args_len = ARRAY_SIZE(l2tp_args) + pppd_argc + 1;
+        char *args[args_len];
+
+        /* Populate args[] from l2tp_args[] and pppd_argv[] */
+        memcpy(args, l2tp_args, sizeof(l2tp_args));
+        memcpy(args + ARRAY_SIZE(l2tp_args), pppd_argv,
+                sizeof(char *) * pppd_argc);
+        args[args_len - 1] = NULL;
+
+        execvp("pppd", args);
+        log_print(FATAL, "Exec() %s", strerror(errno));
+        exit(SYSTEM_ERROR); /* Pretending a fatal error in pppd. */
+    }
+
+    log_print(INFO, "Pppd started (pid = %d)", pppd_pid);
+
+ret:
+    close(session_fd);
+    close(tunnel_fd);
+}
diff --git a/mtpd.h b/mtpd.h
index 8e7799b..dad3ea6 100644
--- a/mtpd.h
+++ b/mtpd.h
@@ -70,6 +70,8 @@
 void log_print(int level, char *format, ...);
 void create_socket(int family, int type, char *server, char *port);
 void start_pppd(int pppox);
+void start_pppd_ol2tp(int tunnel_fd, int session_fd, int tunnel_id,
+                      int session_id);
 
 /* Each protocol must implement everything defined in this structure. Note that
  * timeout intervals are in milliseconds, where zero means forever. To indicate