Browse code

Implement the compression V2 data format for stub and lz4.

Patch V2: Fix minor issues found by Steffan
Patch V3: split wire codes and compression flags
Patch V4: Fix further issues reported by Gert
Patch V5: really fix the issues that should be fixed in v2
Patch V6: fix more minor things

It has been tested against v3 server and again itself. From James Mail:

Compression V2

I have observed that compression in many cases, even when
enabled, often does not produce packet size reduction
because much of the packet data typically generated by web
sessions is already compressed. Further, the single byte that
precedes the packet and indicates whether or not compression
occurred has the unfortunate side effect of misaligning the IP
packet in cases where compression did not occur. To remedy this,
I propose a Compression V2 header that is optimized for the
case where compression does not occur.

a. No compression occurred and first byte of IP/Ethernet packet
is NOT 0x50 (0 bytes of overhead and maintains alignment):

[ uncompressed IP/Ethernet packet ]

b. No compression occurred and first byte of IP/Ethernet packet
is 0x50 (2 bytes of overhead but unlikely since no known
IP packet can begin with 0x50):

[ 0x50 ] [ 0x00 ] [ uncompressed IP/Ethernet packet ]

c. Compression occurred (2 bytes of overhead):

[ 0x50 ] [ compression Alg ID ] [ compressed IP/Ethernet packet ]

Compression Alg ID is one-byte algorithm identifier
for LZ4 (0x1), LZO (0x2), or Snappy (0x3).

This approach has several beneficial effects:

1. In the common case where compression does not occur, no
compression op is required, therefore there is zero overhead.

2. When compression does not occur, the IP/Ethernet packet
alignment is retained.

3. This technique does not require any byte swapping with
the tail of the packet which can potentially incur an
expensive cache miss.

Acked-by: Steffan Karger <steffan.karger@fox-it.com>
Acked-by: Gert Doering <gert@greenie.muc.de>
Message-Id: <1451842066-13475-1-git-send-email-arne@rfc2549.org>
URL: http://article.gmane.org/gmane.network.openvpn.devel/10925

Signed-off-by: Gert Doering <gert@greenie.muc.de>

Arne Schwabe authored on 2016/01/04 02:27:46
Showing 7 changed files
... ...
@@ -44,9 +44,6 @@
44 44
 
45 45
 #include "memdbg.h"
46 46
 
47
-/* Initial command byte to tell our peer if we compressed */
48
-#define LZ4_COMPRESS_BYTE 0x69
49
-
50 47
 static void
51 48
 lz4_compress_init (struct compress_context *compctx)
52 49
 {
... ...
@@ -55,20 +52,22 @@ lz4_compress_init (struct compress_context *compctx)
55 55
 }
56 56
 
57 57
 static void
58
-lz4_compress_uninit (struct compress_context *compctx)
58
+lz4v2_compress_init (struct compress_context *compctx)
59 59
 {
60
+  msg (D_INIT_MEDIUM, "LZ4v2 compression initializing");
60 61
 }
61 62
 
62 63
 static void
63
-lz4_compress (struct buffer *buf, struct buffer work,
64
-	       struct compress_context *compctx,
65
-	       const struct frame* frame)
64
+lz4_compress_uninit (struct compress_context *compctx)
66 65
 {
67
-  bool compressed = false;
68
-
69
-  if (buf->len <= 0)
70
-    return;
66
+}
71 67
 
68
+static bool
69
+do_lz4_compress (struct buffer *buf,
70
+		 struct buffer *work,
71
+		 struct compress_context *compctx,
72
+		 const struct frame* frame)
73
+{
72 74
   /*
73 75
    * In order to attempt compression, length must be at least COMPRESS_THRESHOLD.
74 76
    */
... ...
@@ -78,33 +77,52 @@ lz4_compress (struct buffer *buf, struct buffer work,
78 78
       int zlen_max = ps + COMP_EXTRA_BUFFER (ps);
79 79
       int zlen;
80 80
 
81
-      ASSERT (buf_init (&work, FRAME_HEADROOM (frame)));
82
-      ASSERT (buf_safe (&work, zlen_max));
81
+      ASSERT (buf_init (work, FRAME_HEADROOM (frame)));
82
+      ASSERT (buf_safe (work, zlen_max));
83 83
 
84 84
       if (buf->len > ps)
85 85
 	{
86 86
 	  dmsg (D_COMP_ERRORS, "LZ4 compression buffer overflow");
87 87
 	  buf->len = 0;
88
-	  return;
88
+	  return false;
89 89
 	}
90 90
 
91
-      zlen = LZ4_compress_limitedOutput((const char *)BPTR(buf), (char *)BPTR(&work), BLEN(buf), zlen_max );
91
+      zlen = LZ4_compress_limitedOutput((const char *)BPTR(buf), (char *)BPTR(work), BLEN(buf), zlen_max );
92 92
 
93 93
       if (zlen <= 0)
94 94
 	{
95 95
 	  dmsg (D_COMP_ERRORS, "LZ4 compression error");
96 96
 	  buf->len = 0;
97
-	  return;
97
+	  return false;
98 98
 	}
99 99
 
100
-      ASSERT (buf_safe (&work, zlen));
101
-      work.len = zlen;
102
-      compressed = true;
100
+      ASSERT (buf_safe (work, zlen));
101
+      work->len = zlen;
102
+
103 103
 
104
-      dmsg (D_COMP, "LZ4 compress %d -> %d", buf->len, work.len);
104
+      dmsg (D_COMP, "LZ4 compress %d -> %d", buf->len, work->len);
105 105
       compctx->pre_compress += buf->len;
106
-      compctx->post_compress += work.len;
106
+      compctx->post_compress += work->len;
107
+      return true;
107 108
     }
109
+  return false;
110
+}
111
+
112
+
113
+static void
114
+lz4_compress (struct buffer *buf, struct buffer work,
115
+	      struct compress_context *compctx,
116
+	      const struct frame* frame)
117
+{
118
+  bool compressed;
119
+  if (buf->len <= 0)
120
+    return;
121
+
122
+  compressed = do_lz4_compress(buf, &work, compctx, frame);
123
+
124
+  /* On error do_lz4_compress sets buf len to zero, just return */
125
+  if (buf->len == 0)
126
+    return;
108 127
 
109 128
   /* did compression save us anything? */
110 129
   {
... ...
@@ -128,13 +146,70 @@ lz4_compress (struct buffer *buf, struct buffer work,
128 128
   }
129 129
 }
130 130
 
131
+
132
+static void
133
+lz4v2_compress (struct buffer *buf, struct buffer work,
134
+		struct compress_context *compctx,
135
+		const struct frame* frame)
136
+{
137
+  bool compressed;
138
+  if (buf->len <= 0)
139
+    return;
140
+
141
+  compressed = do_lz4_compress(buf, &work, compctx, frame);
142
+
143
+  /* On Error just return */
144
+  if (buf->len == 0)
145
+    return;
146
+
147
+  /* did compression save us anything?  Include 2 byte compression header
148
+     in calculation */
149
+  if (compressed && work.len + 2 < buf->len)
150
+    {
151
+      ASSERT(buf_prepend(&work, 2));
152
+      uint8_t *head = BPTR (&work);
153
+      head[0] = COMP_ALGV2_INDICATOR_BYTE;
154
+      head[1] = COMP_ALGV2_LZ4_BYTE;
155
+      *buf = work;
156
+    }
157
+  else
158
+    {
159
+      compv2_escape_data_ifneeded(buf);
160
+    }
161
+}
162
+
163
+void
164
+do_lz4_decompress(size_t zlen_max,
165
+		  struct buffer *work,
166
+		  struct buffer *buf,
167
+		  struct compress_context *compctx)
168
+{
169
+  int uncomp_len;
170
+  ASSERT (buf_safe (work, zlen_max));
171
+  uncomp_len = LZ4_decompress_safe((const char *)BPTR(buf), (char *)BPTR(work), (size_t)BLEN(buf), zlen_max);
172
+  if (uncomp_len <= 0)
173
+    {
174
+      dmsg (D_COMP_ERRORS, "LZ4 decompression error: %d", uncomp_len);
175
+      buf->len = 0;
176
+      return;
177
+    }
178
+
179
+  ASSERT (buf_safe (work, uncomp_len));
180
+  work->len = uncomp_len;
181
+
182
+  dmsg (D_COMP, "LZ4 decompress %d -> %d", buf->len, work->len);
183
+  compctx->pre_decompress += buf->len;
184
+  compctx->post_decompress += work->len;
185
+
186
+  *buf = *work;
187
+}
188
+
131 189
 static void
132 190
 lz4_decompress (struct buffer *buf, struct buffer work,
133
-		 struct compress_context *compctx,
134
-		 const struct frame* frame)
191
+		struct compress_context *compctx,
192
+		const struct frame* frame)
135 193
 {
136 194
   size_t zlen_max = EXPANDED_SIZE (frame);
137
-  int uncomp_len;
138 195
   uint8_t c;		/* flag indicating whether or not our peer compressed */
139 196
 
140 197
   if (buf->len <= 0)
... ...
@@ -152,23 +227,7 @@ lz4_decompress (struct buffer *buf, struct buffer work,
152 152
 
153 153
   if (c == LZ4_COMPRESS_BYTE)	/* packet was compressed */
154 154
     {
155
-      ASSERT (buf_safe (&work, zlen_max));
156
-      uncomp_len = LZ4_decompress_safe((const char *)BPTR(buf), (char *)BPTR(&work), (size_t)BLEN(buf), zlen_max);
157
-      if (uncomp_len <= 0)
158
-	{
159
-	  dmsg (D_COMP_ERRORS, "LZ4 decompression error: %d", uncomp_len);
160
-	  buf->len = 0;
161
-	  return;
162
-	}
163
-
164
-      ASSERT (buf_safe (&work, uncomp_len));
165
-      work.len = uncomp_len;
166
-
167
-      dmsg (D_COMP, "LZ4 decompress %d -> %d", buf->len, work.len);
168
-      compctx->pre_decompress += buf->len;
169
-      compctx->post_decompress += work.len;
170
-
171
-      *buf = work;
155
+      do_lz4_decompress(zlen_max, &work, buf, compctx);
172 156
     }
173 157
   else if (c == NO_COMPRESS_BYTE_SWAP)	/* packet was not compressed */
174 158
     {
... ...
@@ -181,6 +240,52 @@ lz4_decompress (struct buffer *buf, struct buffer work,
181 181
     }
182 182
 }
183 183
 
184
+static void
185
+lz4v2_decompress (struct buffer *buf, struct buffer work,
186
+		  struct compress_context *compctx,
187
+		  const struct frame* frame)
188
+{
189
+  size_t zlen_max = EXPANDED_SIZE (frame);
190
+  uint8_t c;		/* flag indicating whether or not our peer compressed */
191
+
192
+  if (buf->len <= 0)
193
+    return;
194
+
195
+  ASSERT (buf_init (&work, FRAME_HEADROOM (frame)));
196
+
197
+  /* do unframing/swap (assumes buf->len > 0) */
198
+  uint8_t *head = BPTR (buf);
199
+  c = *head;
200
+
201
+  /* Not compressed */
202
+  if (c != COMP_ALGV2_INDICATOR_BYTE) {
203
+    return;
204
+  }
205
+
206
+  /* Packet to short to make sense */
207
+  if (buf->len <= 1)
208
+    {
209
+      buf->len=0;
210
+      return;
211
+    }
212
+
213
+  c = head[1];
214
+  if (c == COMP_ALGV2_LZ4_BYTE) /* packet was compressed */
215
+    {
216
+      buf_advance(buf,2);
217
+      do_lz4_decompress(zlen_max, &work, buf, compctx);
218
+    }
219
+  else if (c == COMP_ALGV2_UNCOMPRESSED_BYTE)
220
+    {
221
+      buf_advance(buf,2);
222
+    }
223
+  else
224
+    {
225
+      dmsg (D_COMP_ERRORS, "Bad LZ4v2 decompression header byte: %d", c);
226
+      buf->len = 0;
227
+    }
228
+}
229
+
184 230
 const struct compress_alg lz4_alg = {
185 231
   "lz4",
186 232
   lz4_compress_init,
... ...
@@ -189,6 +294,14 @@ const struct compress_alg lz4_alg = {
189 189
   lz4_decompress
190 190
 };
191 191
 
192
+const struct compress_alg lz4v2_alg = {
193
+  "lz4v2",
194
+  lz4v2_compress_init,
195
+  lz4_compress_uninit,
196
+  lz4v2_compress,
197
+  lz4v2_decompress
198
+};
199
+
192 200
 #else
193 201
 static void dummy(void) {}
194 202
 #endif /* ENABLE_LZ4 */
... ...
@@ -31,6 +31,7 @@
31 31
 #include "buffer.h"
32 32
 
33 33
 extern const struct compress_alg lz4_alg;
34
+extern const struct compress_alg lz4v2_alg;
34 35
 
35 36
 struct lz4_workspace
36 37
 {
... ...
@@ -50,6 +50,11 @@ comp_init(const struct compress_options *opt)
50 50
       compctx->alg = comp_stub_alg;
51 51
       (*compctx->alg.compress_init)(compctx);
52 52
       break;
53
+    case COMP_ALGV2_UNCOMPRESSED:
54
+      ALLOC_OBJ_CLEAR (compctx, struct compress_context);
55
+      compctx->flags = opt->flags;
56
+      compctx->alg = compv2_stub_alg;
57
+      break;
53 58
 #ifdef ENABLE_LZO
54 59
     case COMP_ALG_LZO:
55 60
       ALLOC_OBJ_CLEAR (compctx, struct compress_context);
... ...
@@ -65,11 +70,35 @@ comp_init(const struct compress_options *opt)
65 65
       compctx->alg = lz4_alg;
66 66
       (*compctx->alg.compress_init)(compctx);
67 67
       break;
68
+    case COMP_ALGV2_LZ4:
69
+      ALLOC_OBJ_CLEAR (compctx, struct compress_context);
70
+      compctx->flags = opt->flags;
71
+      compctx->alg = lz4v2_alg;
72
+      break;
68 73
 #endif
69 74
     }
70 75
   return compctx;
71 76
 }
72 77
 
78
+/* In the v2 compression schemes, an uncompressed packet has
79
+ * has no opcode in front, unless the first byte is 0x50. In this
80
+ * case the packet needs to be escaped */
81
+void
82
+compv2_escape_data_ifneeded (struct buffer *buf)
83
+{
84
+    uint8_t *head = BPTR (buf);
85
+    if (head[0] != COMP_ALGV2_INDICATOR_BYTE)
86
+	return;
87
+
88
+    /* Header is 0x50 */
89
+    ASSERT(buf_prepend(buf, 2));
90
+
91
+    head = BPTR (buf);
92
+    head[0] = COMP_ALGV2_INDICATOR_BYTE;
93
+    head[1] = COMP_ALGV2_UNCOMPRESSED;
94
+}
95
+
96
+
73 97
 void
74 98
 comp_uninit(struct compress_context *compctx)
75 99
 {
... ...
@@ -120,6 +149,7 @@ comp_generate_peer_info_string(const struct compress_options *opt, struct buffer
120 120
 	{
121 121
 #if defined(ENABLE_LZ4)
122 122
 	  buf_printf (out, "IV_LZ4=1\n");
123
+	  buf_printf (out, "IV_LZ4v2=1\n");
123 124
 #endif
124 125
 #if defined(ENABLE_LZO)
125 126
 	  buf_printf (out, "IV_LZO=1\n");
... ...
@@ -129,6 +159,7 @@ comp_generate_peer_info_string(const struct compress_options *opt, struct buffer
129 129
       if (!lzo_avail)
130 130
 	buf_printf (out, "IV_LZO_STUB=1\n");
131 131
       buf_printf (out, "IV_COMP_STUB=1\n");
132
+      buf_printf (out, "IV_COMP_STUBv2=1\n");
132 133
     }
133 134
 }
134 135
 
... ...
@@ -43,12 +43,22 @@
43 43
 #define COMP_ALG_SNAPPY 3 /* Snappy algorithm (no longer supported) */
44 44
 #define COMP_ALG_LZ4    4 /* LZ4 algorithm */
45 45
 
46
+
47
+/* algorithm v2 */
48
+#define COMP_ALGV2_UNCOMPRESSED 10
49
+#define COMP_ALGV2_LZ4	    11
50
+/*
51
+#define COMP_ALGV2_LZO	    12
52
+#define COMP_ALGV2_SNAPPY   13
53
+*/
54
+
46 55
 /* Compression flags */
47 56
 #define COMP_F_ADAPTIVE   (1<<0) /* COMP_ALG_LZO only */
48 57
 #define COMP_F_ASYM       (1<<1) /* only downlink is compressed, not uplink */
49 58
 #define COMP_F_SWAP       (1<<2) /* initial command byte is swapped with last byte in buffer to preserve payload alignment */
50 59
 #define COMP_F_ADVERTISE_STUBS_ONLY (1<<3) /* tell server that we only support compression stubs */
51 60
 
61
+
52 62
 /*
53 63
  * Length of prepended prefix on compressed packets
54 64
  */
... ...
@@ -57,9 +67,21 @@
57 57
 /*
58 58
  * Prefix bytes
59 59
  */
60
+
61
+/* V1 on wire codes */
62
+/* Initial command byte to tell our peer if we compressed */
63
+#define LZO_COMPRESS_BYTE 0x66
64
+#define LZ4_COMPRESS_BYTE 0x69
60 65
 #define NO_COMPRESS_BYTE      0xFA
61 66
 #define NO_COMPRESS_BYTE_SWAP 0xFB /* to maintain payload alignment, replace this byte with last byte of packet */
62 67
 
68
+/* V2 on wire code */
69
+#define COMP_ALGV2_INDICATOR_BYTE	0x50
70
+#define COMP_ALGV2_UNCOMPRESSED_BYTE	0
71
+#define COMP_ALGV2_LZ4_BYTE		1
72
+#define COMP_ALGV2_LZO_BYTE		2
73
+#define COMP_ALGV2_SNAPPY_BYTE		3
74
+
63 75
 /*
64 76
  * Compress worst case size expansion (for any algorithm)
65 77
  *
... ...
@@ -145,6 +167,7 @@ struct compress_context
145 145
 };
146 146
 
147 147
 extern const struct compress_alg comp_stub_alg;
148
+extern const struct compress_alg compv2_stub_alg;
148 149
 
149 150
 struct compress_context *comp_init(const struct compress_options *opt);
150 151
 
... ...
@@ -157,6 +180,8 @@ void comp_print_stats (const struct compress_context *compctx, struct status_out
157 157
 
158 158
 void comp_generate_peer_info_string(const struct compress_options *opt, struct buffer *out);
159 159
 
160
+void compv2_escape_data_ifneeded (struct buffer *buf);
161
+
160 162
 static inline bool
161 163
 comp_enabled(const struct compress_options *info)
162 164
 {
... ...
@@ -105,6 +105,57 @@ stub_decompress (struct buffer *buf, struct buffer work,
105 105
     }
106 106
 }
107 107
 
108
+
109
+static void
110
+stubv2_compress (struct buffer *buf, struct buffer work,
111
+		 struct compress_context *compctx,
112
+		 const struct frame* frame)
113
+{
114
+    if (buf->len <= 0)
115
+	return;
116
+
117
+    compv2_escape_data_ifneeded (buf);
118
+}
119
+
120
+static void
121
+stubv2_decompress (struct buffer *buf, struct buffer work,
122
+		   struct compress_context *compctx,
123
+		   const struct frame* frame)
124
+{
125
+  if (buf->len <= 0)
126
+    return;
127
+
128
+  uint8_t *head = BPTR (buf);
129
+
130
+  /* no compression or packet to short*/
131
+  if (head[0] != COMP_ALGV2_INDICATOR_BYTE)
132
+    return;
133
+
134
+  /* compression header (0x50) is present */
135
+  buf_advance(buf, 1);
136
+
137
+  /* Packet buffer too short (only 1 byte) */
138
+  if (buf->len <= 0)
139
+    return;
140
+
141
+  head = BPTR (buf);
142
+  buf_advance(buf, 1);
143
+
144
+  if (head[0] != COMP_ALGV2_UNCOMPRESSED_BYTE) {
145
+    dmsg (D_COMP_ERRORS, "Bad compression stubv2 decompression header byte: %d", *head);
146
+    buf->len = 0;
147
+    return;
148
+  }
149
+}
150
+
151
+const struct compress_alg compv2_stub_alg = {
152
+  "stubv2",
153
+  stub_compress_init,
154
+  stub_compress_uninit,
155
+  stubv2_compress,
156
+  stubv2_decompress
157
+};
158
+
108 159
 const struct compress_alg comp_stub_alg = {
109 160
   "stub",
110 161
   stub_compress_init,
... ...
@@ -42,9 +42,6 @@
42 42
 
43 43
 #include "memdbg.h"
44 44
 
45
-/* Initial command byte to tell our peer if we compressed */
46
-#define LZO_COMPRESS_BYTE 0x66
47
-
48 45
 /**
49 46
  * Perform adaptive compression housekeeping.
50 47
  *
... ...
@@ -6344,16 +6344,21 @@ add_option (struct options *options,
6344 6344
       VERIFY_PERMISSION (OPT_P_COMP);
6345 6345
       if (p[1])
6346 6346
 	{
6347
+	  options->comp.flags = 0;
6347 6348
 	  if (streq (p[1], "stub"))
6348 6349
 	    {
6349 6350
 	      options->comp.alg = COMP_ALG_STUB;
6350 6351
 	      options->comp.flags = (COMP_F_SWAP|COMP_F_ADVERTISE_STUBS_ONLY);
6351 6352
 	    }
6353
+	  else if (streq(p[1], "stub-v2"))
6354
+	    {
6355
+	      options->comp.alg = COMP_ALGV2_UNCOMPRESSED;
6356
+	      options->comp.flags = COMP_F_ADVERTISE_STUBS_ONLY;
6357
+	    }
6352 6358
 #if defined(ENABLE_LZO)
6353 6359
 	  else if (streq (p[1], "lzo"))
6354 6360
 	    {
6355 6361
 	      options->comp.alg = COMP_ALG_LZO;
6356
-	      options->comp.flags = 0;
6357 6362
 	    }
6358 6363
 #endif
6359 6364
 #if defined(ENABLE_LZ4)
... ...
@@ -6362,6 +6367,10 @@ add_option (struct options *options,
6362 6362
 	      options->comp.alg = COMP_ALG_LZ4;
6363 6363
 	      options->comp.flags = COMP_F_SWAP;
6364 6364
 	    }
6365
+	  else if (streq (p[1], "lz4-v2"))
6366
+	    {
6367
+	      options->comp.alg = COMP_ALGV2_LZ4;
6368
+	    }
6365 6369
 #endif
6366 6370
 	  else
6367 6371
 	    {