Hi,

Same patch with added NULL check in push.c:308. Turns out that
peer_info might be NULL.

-Lev

On Wed, Mar 26, 2014 at 10:52 AM, Lev Stipakov <lstipa...@gmail.com> wrote:
> Hello,
>
> Despite that implementation of session-id has already been discussed,
> I would like to propose an alternative approach.
>
> I suggest to use an array of multi_instance objects and session-id as
> an item's index in that array. Why I think it is a good idea?
>
> 1) We could utilize less bytes for session-id, since array's max
> length would be equal to max_clients. 3 bytes for a max of 2**24
> clients, no need to swap one byte to the end of packet.
>
> [op][s1][s2][s3][data]
>
> 2) Lookup in array is faster than lookup by 8-byte session-id in
> hashtable and happens with constant speed.
>
> 3) Proof of concept is attached!
>
> I have tested it with openvpn master branch (server) and ics-openvpn
> (android) - merged without any conflicts and works nicely. Of course
> it supports also peers without session-id support.
>
> This patch solves 2 issues for mobile clients:
>
> 1) UDP NAT timeout. After, say, 30, seconds router might close UDP
> socket and new packet from client to VPN server originates from
> different port. Lookup by peer address fails and packet got dropped.
> Currently this is solved by small keep-alive interval, which drains
> battery. With new implementation it is not an issue anymore since
> lookup is done by session-id, therefore keep-alive interval can be
> increased.
>
> 2) Transition between networks (for example WiFi->3G). Without
> session-id client got disconnected. With this patch and
> http://code.google.com/p/ics-openvpn/source/detail?r=bc9f3f448b9a4d95c999d3e582987840ba1e0fbf
> transition happens seamlessly.
>
> I would love to hear any critics / comments!
>
> --
> -Lev



-- 
-Lev
From 0f330408e4b013cd505ad9492888557daa47e905 Mon Sep 17 00:00:00 2001
From: Lev Stipakov <lev.stipa...@f-secure.com>
Date: Tue, 11 Mar 2014 17:58:31 +0200
Subject: [PATCH] Floating implementation. Use array lookup for new opcode
 P_DATA_V2 and check HMAC for floated peers.

---
 src/openvpn/crypto.c     |  54 ++++++++++++++++++++++++
 src/openvpn/crypto.h     |   3 ++
 src/openvpn/mudp.c       | 106 ++++++++++++++++++++++++++++++++++++++++++++---
 src/openvpn/multi.c      |  14 ++++++-
 src/openvpn/multi.h      |   2 +
 src/openvpn/options.c    |  15 ++++++-
 src/openvpn/options.h    |   4 +-
 src/openvpn/push.c       |   8 +++-
 src/openvpn/ssl.c        |  59 +++++++++++++++++++++++---
 src/openvpn/ssl.h        |   9 +++-
 src/openvpn/ssl_common.h |   4 ++
 11 files changed, 259 insertions(+), 19 deletions(-)

diff --git a/src/openvpn/crypto.c b/src/openvpn/crypto.c
index c4c356d..8c0d33f 100644
--- a/src/openvpn/crypto.c
+++ b/src/openvpn/crypto.c
@@ -389,6 +389,60 @@ openvpn_decrypt (struct buffer *buf, struct buffer work,
 }
 
 /*
+ * This verifies if a packet and its HMAC fit to a crypto context.
+ *
+ * On success true is returned.
+ */
+bool
+crypto_test_hmac (struct buffer *buf, const struct crypto_options *opt)
+{
+  struct gc_arena gc;
+  gc_init (&gc);
+  int offset = 4; /* 1 byte opcode + 3 bytes session-id */
+
+  if (buf->len > 0 && opt->key_ctx_bi)
+    {
+      struct key_ctx *ctx = &opt->key_ctx_bi->decrypt;
+
+      /* Verify the HMAC */
+      if (ctx->hmac)
+	{
+	  int hmac_len;
+	  uint8_t local_hmac[MAX_HMAC_KEY_LENGTH]; /* HMAC of ciphertext computed locally */
+
+	  hmac_ctx_reset(ctx->hmac);
+
+	  /* Assume the length of the input HMAC */
+	  hmac_len = hmac_ctx_size (ctx->hmac);
+
+	  /* Authentication fails if insufficient data in packet for HMAC */
+	  if ((buf->len - offset) < hmac_len)
+	    {
+	      gc_free (&gc);
+	      return false;
+	    }
+
+	  hmac_ctx_update (ctx->hmac, BPTR (buf) + offset + hmac_len,
+			   BLEN (buf) - offset - hmac_len);
+	  hmac_ctx_final (ctx->hmac, local_hmac);
+
+	  /* Compare locally computed HMAC with packet HMAC */
+	  if (memcmp (local_hmac, BPTR (buf) + offset, hmac_len))
+	    {
+	      gc_free (&gc);
+	      return false;
+	    }
+
+	  gc_free (&gc);
+	  return true;
+	}
+    }
+
+  gc_free (&gc);
+  return false;
+}
+
+/*
  * How many bytes will we add to frame buffer for a given
  * set of crypto options?
  */
diff --git a/src/openvpn/crypto.h b/src/openvpn/crypto.h
index 3b4b88e..68cdf16 100644
--- a/src/openvpn/crypto.h
+++ b/src/openvpn/crypto.h
@@ -279,6 +279,9 @@ bool openvpn_decrypt (struct buffer *buf, struct buffer work,
 		      const struct crypto_options *opt,
 		      const struct frame* frame);
 
+
+bool crypto_test_hmac (struct buffer *buf, const struct crypto_options *opt);
+
 /** @} name Functions for performing security operations on data channel packets */
 
 void crypto_adjust_frame_parameters(struct frame *frame,
diff --git a/src/openvpn/mudp.c b/src/openvpn/mudp.c
index 3468dab..f7ab625 100644
--- a/src/openvpn/mudp.c
+++ b/src/openvpn/mudp.c
@@ -38,6 +38,55 @@
 #include "memdbg.h"
 
 /*
+ * Update instance with new peer address
+ */
+void
+update_floated(struct multi_context *m, struct multi_instance *mi,
+	       struct mroute_addr real, uint32_t hv)
+{
+  struct mroute_addr real_old;
+
+  real_old = mi->real;
+  generate_prefix (mi);
+
+  /* remove before modifying mi->real, since it also modifies key in hash */
+  hash_remove(m->hash, &real_old);
+  hash_remove(m->iter, &real_old);
+
+  /* update address */
+  memcpy(&mi->real, &real, sizeof(real));
+
+  mi->context.c2.from = m->top.c2.from;
+  mi->context.c2.to_link_addr = &mi->context.c2.from;
+
+  /* switch to new log prefix */
+  generate_prefix (mi);
+  /* inherit buffers */
+  mi->context.c2.buffers = m->top.c2.buffers;
+
+  /* inherit parent link_socket and link_socket_info */
+  mi->context.c2.link_socket = m->top.c2.link_socket;
+  mi->context.c2.link_socket_info->lsa->actual = m->top.c2.from;
+
+  /* fix remote_addr in tls structure */
+  tls_update_remote_addr (mi->context.c2.tls_multi, &mi->context.c2.from);
+  mi->did_open_context = true;
+
+  hash_add(m->hash, &mi->real, mi, false);
+  hash_add(m->iter, &mi->real, mi, false);
+
+  mi->did_real_hash = true;
+#ifdef MANAGEMENT_DEF_AUTH
+  hash_remove (m->cid_hash, &mi->context.c2.mda_context.cid);
+  hash_add (m->cid_hash, &mi->context.c2.mda_context.cid, mi, false);
+#endif
+
+#ifdef MANAGEMENT_DEF_AUTH
+  mi->did_cid_hash = true;
+#endif
+}
+
+/*
  * Get a client instance based on real address.  If
  * the instance doesn't exist, create it while
  * maintaining real address hash table atomicity.
@@ -56,15 +105,47 @@ multi_get_create_instance_udp (struct multi_context *m)
       struct hash_element *he;
       const uint32_t hv = hash_value (hash, &real);
       struct hash_bucket *bucket = hash_bucket (hash, hv);
-  
-      he = hash_lookup_fast (hash, bucket, &real, hv);
+      uint8_t* ptr  = BPTR(&m->top.c2.buf);
+      uint8_t op = ptr[0] >> P_OPCODE_SHIFT;
+      uint32_t sess_id;
+      bool session_forged = false;
 
-      if (he)
+      if (op == P_DATA_V2)
 	{
-	  mi = (struct multi_instance *) he->value;
+	  sess_id = (*(uint32_t*)ptr) >> 8;
+	  if ((sess_id < m->max_clients) && (m->instances[sess_id]))
+	    {
+	      mi = m->instances[sess_id];
+
+	      if (!link_socket_actual_match(&mi->context.c2.from, &m->top.c2.from))
+		{
+		  msg(D_MULTI_MEDIUM, "floating detected from %s to %s",
+		      print_link_socket_actual (&mi->context.c2.from, &gc), print_link_socket_actual (&m->top.c2.from, &gc));
+
+		  /* session-id is not trusted, so check hmac */
+		  session_forged = !(crypto_test_hmac(&m->top.c2.buf, &mi->context.c2.crypto_options));
+		  if (session_forged)
+		    {
+		      mi = NULL;
+		      msg (D_MULTI_MEDIUM, "hmac verification failed, session forge detected!");
+		    }
+		  else
+		    {
+		      update_floated(m, mi, real, hv);
+		    }
+		}
+	    }
 	}
       else
 	{
+	  he = hash_lookup_fast (hash, bucket, &real, hv);
+	  if (he)
+	    {
+	      mi = (struct multi_instance *) he->value;
+	    }
+	}
+      if (!mi && !session_forged)
+	{
 	  if (!m->top.c2.tls_auth_standalone
 	      || tls_pre_decrypt_lite (m->top.c2.tls_auth_standalone, &m->top.c2.from, &m->top.c2.buf))
 	    {
@@ -75,6 +156,17 @@ multi_get_create_instance_udp (struct multi_context *m)
 		    {
 		      hash_add_fast (hash, bucket, &mi->real, hv, mi);
 		      mi->did_real_hash = true;
+
+		      int i;
+		      for (i = 0; i < m->max_clients; ++ i)
+			{
+			  if (!m->instances[i])
+			    {
+			      mi->context.c2.tls_multi->vpn_session_id = i;
+			      m->instances[i] = mi;
+			      break;
+			    }
+			}
 		    }
 		}
 	      else
@@ -89,15 +181,17 @@ multi_get_create_instance_udp (struct multi_context *m)
 #ifdef ENABLE_DEBUG
       if (check_debug_level (D_MULTI_DEBUG))
 	{
-	  const char *status;
+	  const char *status = mi ? "[ok]" : "[failed]";
 
+	  /*
 	  if (he && mi)
 	    status = "[succeeded]";
 	  else if (!he && mi)
 	    status = "[created]";
 	  else
 	    status = "[failed]";
-	
+	  */
+
 	  dmsg (D_MULTI_DEBUG, "GET INST BY REAL: %s %s",
 	       mroute_addr_print (&real, &gc),
 	       status);
diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c
index 2839b30..618ade5 100644
--- a/src/openvpn/multi.c
+++ b/src/openvpn/multi.c
@@ -302,6 +302,7 @@ multi_init (struct multi_context *m, struct context *t, bool tcp_mode, int threa
 			   cid_compare_function);
 #endif
 
+
   /*
    * This is our scheduler, for time-based wakeup
    * events.
@@ -372,6 +373,13 @@ multi_init (struct multi_context *m, struct context *t, bool tcp_mode, int threa
    */
   m->max_clients = t->options.max_clients;
 
+  int i;
+  m->instances = malloc(sizeof(struct multi_instance*) * m->max_clients);
+  for (i = 0; i < m->max_clients; ++ i)
+    {
+      m->instances[i] = NULL;
+    }
+
   /*
    * Initialize multi-socket TCP I/O wait object
    */
@@ -552,6 +560,8 @@ multi_close_instance (struct multi_context *m,
 	}
 #endif
 
+      m->instances[mi->context.c2.tls_multi->vpn_session_id] = NULL;
+
       schedule_remove_entry (m->schedule, (struct schedule_entry *) mi);
 
       ifconfig_pool_release (m->ifconfig_pool, mi->vaddr_handle, false);
@@ -628,6 +638,8 @@ multi_uninit (struct multi_context *m)
 #endif
 	  m->hash = NULL;
 
+	  free(m->instances);
+
 	  schedule_free (m->schedule);
 	  mbuf_free (m->mbuf);
 	  ifconfig_pool_free (m->ifconfig_pool);
@@ -651,8 +663,6 @@ multi_create_instance (struct multi_context *m, const struct mroute_addr *real)
 
   perf_push (PERF_MULTI_CREATE_INSTANCE);
 
-  msg (D_MULTI_MEDIUM, "MULTI: multi_create_instance called");
-
   ALLOC_OBJ_CLEAR (mi, struct multi_instance);
 
   mi->gc = gc_new ();
diff --git a/src/openvpn/multi.h b/src/openvpn/multi.h
index fc2ffb2..0446fbf 100644
--- a/src/openvpn/multi.h
+++ b/src/openvpn/multi.h
@@ -125,6 +125,8 @@ struct multi_context {
 # define MC_WORK_THREAD                (MC_MULTI_THREADED_WORKER|MC_MULTI_THREADED_SCHEDULER)
   int thread_mode;
 
+  struct multi_instance** instances;
+
   struct hash *hash;            /**< VPN tunnel instances indexed by real
                                  *   address of the remote peer. */
   struct hash *vhash;           /**< VPN tunnel instances indexed by
diff --git a/src/openvpn/options.c b/src/openvpn/options.c
index ef6170c..8e44e06 100644
--- a/src/openvpn/options.c
+++ b/src/openvpn/options.c
@@ -3908,7 +3908,8 @@ apply_push_options (struct options *options,
 		    struct buffer *buf,
 		    unsigned int permission_mask,
 		    unsigned int *option_types_found,
-		    struct env_set *es)
+		    struct env_set *es,
+		    struct tls_multi *tls_multi)
 {
   char line[OPTION_PARM_SIZE];
   int line_num = 0;
@@ -3922,7 +3923,17 @@ apply_push_options (struct options *options,
       ++line_num;
       if (parse_line (line, p, SIZE (p), file, line_num, msglevel, &options->gc))
 	{
-	  add_option (options, p, file, line_num, 0, msglevel, permission_mask, option_types_found, es);
+	  if (streq(p[0], "session_id"))
+	    {
+	      /* Server supports P_DATA_V2 */
+	      tls_multi->vpn_session_id = atoi(p[1]);
+	      tls_multi->use_session_id = true;
+	      msg(D_PUSH, "session id: %d", tls_multi->vpn_session_id);
+	    }
+	  else
+	    {
+	      add_option (options, p, file, line_num, 0, msglevel, permission_mask, option_types_found, es);
+	    }
 	}
     }
   return true;
diff --git a/src/openvpn/options.h b/src/openvpn/options.h
index 27bbc14..295a0c8 100644
--- a/src/openvpn/options.h
+++ b/src/openvpn/options.h
@@ -716,11 +716,13 @@ void options_postprocess (struct options *options);
 void pre_pull_save (struct options *o);
 void pre_pull_restore (struct options *o, struct gc_arena *gc);
 
+struct tls_multi;
 bool apply_push_options (struct options *options,
 			 struct buffer *buf,
 			 unsigned int permission_mask,
 			 unsigned int *option_types_found,
-			 struct env_set *es);
+			 struct env_set *es,
+			 struct tls_multi* tls_multi);
 
 void options_detach (struct options *o);
 
diff --git a/src/openvpn/push.c b/src/openvpn/push.c
index 606bb05..b777ae2 100644
--- a/src/openvpn/push.c
+++ b/src/openvpn/push.c
@@ -304,6 +304,11 @@ send_push_reply (struct context *c)
   if (multi_push)
     buf_printf (&buf, ",push-continuation 1");
 
+  /* Send session_id if client supports it */
+  if (c->c2.tls_multi->peer_info && strstr(c->c2.tls_multi->peer_info, "IV_PROTO=2")) {
+      buf_printf(&buf, ",session_id %d", c->c2.tls_multi->vpn_session_id);
+  }
+
   if (BLEN (&buf) > sizeof(cmd)-1)
     {
       const bool status = send_control_channel_string (c, BSTR (&buf), D_PUSH);
@@ -463,7 +468,8 @@ process_incoming_push_msg (struct context *c,
 				  &buf,
 				  permission_mask,
 				  option_types_found,
-				  c->c2.es))
+				  c->c2.es,
+				  c->c2.tls_multi))
 	    switch (c->options.push_continuation)
 	      {
 	      case 0:
diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c
index c61701a..84f2b16 100644
--- a/src/openvpn/ssl.c
+++ b/src/openvpn/ssl.c
@@ -611,6 +611,8 @@ packet_opcode_name (int op)
       return "P_ACK_V1";
     case P_DATA_V1:
       return "P_DATA_V1";
+    case P_DATA_V2:
+      return "P_DATA_V2";
     default:
       return "P_???";
     }
@@ -1037,6 +1039,9 @@ tls_multi_init (struct tls_options *tls_options)
   ret->key_scan[1] = &ret->session[TM_ACTIVE].key[KS_LAME_DUCK];
   ret->key_scan[2] = &ret->session[TM_LAME_DUCK].key[KS_LAME_DUCK];
 
+  /* By default not use P_DATA_V2 */
+  ret->use_session_id = false;
+
   return ret;
 }
 
@@ -1810,6 +1815,9 @@ push_peer_info(struct buffer *buf, struct tls_session *session)
       buf_printf (&out, "IV_PLAT=win\n");
 #endif
 
+      /* support for P_DATA_V2 */
+      buf_printf(&out, "IV_PROTO=2\n");
+
       /* push compression status */
 #ifdef USE_COMP
       comp_generate_peer_info_string(&session->opt->comp_options, &out);
@@ -2766,8 +2774,9 @@ tls_pre_decrypt (struct tls_multi *multi,
 	key_id = c & P_KEY_ID_MASK;
       }
 
-      if (op == P_DATA_V1)
-	{			/* data channel packet */
+      if ((op == P_DATA_V1) || (op == P_DATA_V2))
+	{
+	  /* data channel packet */
 	  for (i = 0; i < KEY_SCAN_SIZE; ++i)
 	    {
 	      struct key_state *ks = multi->key_scan[i];
@@ -2799,7 +2808,9 @@ tls_pre_decrypt (struct tls_multi *multi,
 		  opt->pid_persist = NULL;
 		  opt->flags &= multi->opt.crypto_flags_and;
 		  opt->flags |= multi->opt.crypto_flags_or;
-		  ASSERT (buf_advance (buf, 1));
+
+		  ASSERT (buf_advance (buf, op == P_DATA_V1 ? 1 : 4));
+
 		  ++ks->n_packets;
 		  ks->n_bytes += buf->len;
 		  dmsg (D_TLS_KEYSELECT,
@@ -3296,6 +3307,7 @@ tls_pre_decrypt_lite (const struct tls_auth_standalone *tas,
   return ret;
 
  error:
+
   tls_clear_error();
   gc_free (&gc);
   return ret;
@@ -3364,14 +3376,24 @@ tls_post_encrypt (struct tls_multi *multi, struct buffer *buf)
 {
   struct key_state *ks;
   uint8_t *op;
+  uint32_t sess;
 
   ks = multi->save_ks;
   multi->save_ks = NULL;
   if (buf->len > 0)
     {
       ASSERT (ks);
-      ASSERT (op = buf_prepend (buf, 1));
-      *op = (P_DATA_V1 << P_OPCODE_SHIFT) | ks->key_id;
+
+      if (!multi->opt.server && multi->use_session_id)
+	{
+	  sess = ((P_DATA_V2 << P_OPCODE_SHIFT) | ks->key_id) | (multi->vpn_session_id << 8);
+	  ASSERT (buf_write_prepend (buf, &sess, 4));
+	}
+      else
+	{
+	  ASSERT (op = buf_prepend (buf, 1));
+	  *op = (P_DATA_V1 << P_OPCODE_SHIFT) | ks->key_id;
+	}
       ++ks->n_packets;
       ks->n_bytes += buf->len;
     }
@@ -3444,6 +3466,31 @@ tls_rec_payload (struct tls_multi *multi,
   return ret;
 }
 
+/* Update the remote_addr, needed if a client floats. */
+void
+tls_update_remote_addr (struct tls_multi *multi,
+const struct link_socket_actual *from)
+{
+  struct gc_arena gc = gc_new ();
+  int i;
+
+  for (i = 0; i < KEY_SCAN_SIZE; ++i)
+    {
+      struct key_state *ks = multi->key_scan[i];
+      if (DECRYPT_KEY_ENABLED (multi, ks) && ks->authenticated && link_socket_actual_defined(&ks->remote_addr)) 
+       {
+	 if (link_socket_actual_match (from, &ks->remote_addr))
+	   continue;
+	 dmsg (D_TLS_KEYSELECT,
+		"TLS: tls_update_remote_addr from IP=%s to IP=%s",
+	       print_link_socket_actual (&ks->remote_addr, &gc),
+	       print_link_socket_actual (from, &gc));
+	 memcpy(&ks->remote_addr, from, sizeof(*from));
+       }
+    }
+  gc_free (&gc);
+}
+
 /*
  * Dump a human-readable rendition of an openvpn packet
  * into a garbage collectable string which is returned.
@@ -3478,7 +3525,7 @@ protocol_dump (struct buffer *buffer, unsigned int flags, struct gc_arena *gc)
   key_id = c & P_KEY_ID_MASK;
   buf_printf (&out, "%s kid=%d", packet_opcode_name (op), key_id);
 
-  if (op == P_DATA_V1)
+  if ((op == P_DATA_V1) || (op == P_DATA_V2))
     goto print_data;
 
   /*
diff --git a/src/openvpn/ssl.h b/src/openvpn/ssl.h
index cd7cae2..e634b73 100644
--- a/src/openvpn/ssl.h
+++ b/src/openvpn/ssl.h
@@ -61,6 +61,7 @@
 #define P_CONTROL_V1                   4     /* control channel packet (usually TLS ciphertext) */
 #define P_ACK_V1                       5     /* acknowledgement for packets received */
 #define P_DATA_V1                      6     /* data channel packet */
+#define P_DATA_V2                      9     /* data channel packet with session_id */
 
 /* indicates key_method >= 2 */
 #define P_CONTROL_HARD_RESET_CLIENT_V2 7     /* initial key from client, forget previous state */
@@ -68,7 +69,7 @@
 
 /* define the range of legal opcodes */
 #define P_FIRST_OPCODE                 1
-#define P_LAST_OPCODE                  8
+#define P_LAST_OPCODE                  9
 
 /* Should we aggregate TLS
  * acknowledgements, and tack them onto
@@ -431,6 +432,12 @@ bool tls_send_payload (struct tls_multi *multi,
 bool tls_rec_payload (struct tls_multi *multi,
 		      struct buffer *buf);
 
+/*
+ * Update remote address of a tls_multi structure
+ */
+void tls_update_remote_addr (struct tls_multi *multi,
+			     const struct link_socket_actual *from);
+
 #ifdef MANAGEMENT_DEF_AUTH
 static inline char *
 tls_get_peer_info(const struct tls_multi *multi)
diff --git a/src/openvpn/ssl_common.h b/src/openvpn/ssl_common.h
index 04ba789..2fc72aa 100644
--- a/src/openvpn/ssl_common.h
+++ b/src/openvpn/ssl_common.h
@@ -495,6 +495,10 @@ struct tls_multi
   char *peer_info;
 #endif
 
+  /* For P_DATA_V2 */
+  uint32_t vpn_session_id;
+  int use_session_id;
+
   /*
    * Our session objects.
    */
-- 
1.8.3.2

Reply via email to