1 /*
2 * OpenVPN -- An application to securely tunnel IP networks
3 * over a single UDP port, with support for SSL/TLS-based
4 * session authentication and key exchange,
5 * packet encryption, packet authentication, and
6 * packet compression.
7 *
8 * Copyright (C) 2002-2022 OpenVPN Inc <sales@openvpn.net>
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License version 2
12 * as published by the Free Software Foundation.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License along
20 * with this program; if not, write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 */
23
24 /**
25 * @file Data Channel Compression module function definitions.
26 */
27
28 #ifdef HAVE_CONFIG_H
29 #include "config.h"
30 #elif defined(_MSC_VER)
31 #include "config-msvc.h"
32 #endif
33
34 #include "syshead.h"
35
36 #if defined(ENABLE_LZO)
37
38 #include "comp.h"
39 #include "error.h"
40 #include "otime.h"
41
42 #include "memdbg.h"
43
44 /**
45 * Perform adaptive compression housekeeping.
46 *
47 * @param ac the adaptive compression state structure.
48 *
49 * @return
50 */
51 static bool
lzo_adaptive_compress_test(struct lzo_adaptive_compress * ac)52 lzo_adaptive_compress_test(struct lzo_adaptive_compress *ac)
53 {
54 const bool save = ac->compress_state;
55 const time_t local_now = now;
56
57 if (!ac->compress_state)
58 {
59 if (local_now >= ac->next)
60 {
61 if (ac->n_total > AC_MIN_BYTES
62 && (ac->n_total - ac->n_comp) < (ac->n_total / (100 / AC_SAVE_PCT)))
63 {
64 ac->compress_state = true;
65 ac->next = local_now + AC_OFF_SEC;
66 }
67 else
68 {
69 ac->next = local_now + AC_SAMP_SEC;
70 }
71 dmsg(D_COMP, "lzo_adaptive_compress_test: comp=%d total=%d", ac->n_comp, ac->n_total);
72 ac->n_total = ac->n_comp = 0;
73 }
74 }
75 else
76 {
77 if (local_now >= ac->next)
78 {
79 ac->next = local_now + AC_SAMP_SEC;
80 ac->n_total = ac->n_comp = 0;
81 ac->compress_state = false;
82 }
83 }
84
85 if (ac->compress_state != save)
86 {
87 dmsg(D_COMP_LOW, "Adaptive compression state %s", (ac->compress_state ? "OFF" : "ON"));
88 }
89
90 return !ac->compress_state;
91 }
92
93 static inline void
lzo_adaptive_compress_data(struct lzo_adaptive_compress * ac,int n_total,int n_comp)94 lzo_adaptive_compress_data(struct lzo_adaptive_compress *ac, int n_total, int n_comp)
95 {
96 ac->n_total += n_total;
97 ac->n_comp += n_comp;
98 }
99
100 static void
lzo_compress_init(struct compress_context * compctx)101 lzo_compress_init(struct compress_context *compctx)
102 {
103 msg(D_INIT_MEDIUM, "LZO compression initializing");
104 ASSERT(!(compctx->flags & COMP_F_SWAP));
105 compctx->wu.lzo.wmem_size = LZO_WORKSPACE;
106
107 int lzo_status = lzo_init();
108 if (lzo_status != LZO_E_OK)
109 {
110 msg(M_FATAL, "Cannot initialize LZO compression library (lzo_init() returns %d)", lzo_status);
111 }
112 compctx->wu.lzo.wmem = (lzo_voidp) lzo_malloc(compctx->wu.lzo.wmem_size);
113 check_malloc_return(compctx->wu.lzo.wmem);
114 }
115
116 static void
lzo_compress_uninit(struct compress_context * compctx)117 lzo_compress_uninit(struct compress_context *compctx)
118 {
119 lzo_free(compctx->wu.lzo.wmem);
120 compctx->wu.lzo.wmem = NULL;
121 }
122
123 static inline bool
lzo_compression_enabled(struct compress_context * compctx)124 lzo_compression_enabled(struct compress_context *compctx)
125 {
126 if (!(compctx->flags & COMP_F_ALLOW_COMPRESS))
127 {
128 return false;
129 }
130 else
131 {
132 if (compctx->flags & COMP_F_ADAPTIVE)
133 {
134 return lzo_adaptive_compress_test(&compctx->wu.lzo.ac);
135 }
136 else
137 {
138 return true;
139 }
140 }
141 }
142
143 static void
lzo_compress(struct buffer * buf,struct buffer work,struct compress_context * compctx,const struct frame * frame)144 lzo_compress(struct buffer *buf, struct buffer work,
145 struct compress_context *compctx,
146 const struct frame *frame)
147 {
148 lzo_uint zlen = 0;
149 int err;
150 bool compressed = false;
151
152 if (buf->len <= 0)
153 {
154 return;
155 }
156
157 /*
158 * In order to attempt compression, length must be at least COMPRESS_THRESHOLD,
159 * and our adaptive level must give the OK.
160 */
161 if (buf->len >= COMPRESS_THRESHOLD && lzo_compression_enabled(compctx))
162 {
163 const size_t ps = PAYLOAD_SIZE(frame);
164 ASSERT(buf_init(&work, FRAME_HEADROOM(frame)));
165 ASSERT(buf_safe(&work, ps + COMP_EXTRA_BUFFER(ps)));
166
167 if (buf->len > ps)
168 {
169 dmsg(D_COMP_ERRORS, "LZO compression buffer overflow");
170 buf->len = 0;
171 return;
172 }
173
174 err = LZO_COMPRESS(BPTR(buf), BLEN(buf), BPTR(&work), &zlen, compctx->wu.lzo.wmem);
175 if (err != LZO_E_OK)
176 {
177 dmsg(D_COMP_ERRORS, "LZO compression error: %d", err);
178 buf->len = 0;
179 return;
180 }
181
182 ASSERT(buf_safe(&work, zlen));
183 work.len = zlen;
184 compressed = true;
185
186 dmsg(D_COMP, "LZO compress %d -> %d", buf->len, work.len);
187 compctx->pre_compress += buf->len;
188 compctx->post_compress += work.len;
189
190 /* tell adaptive level about our success or lack thereof in getting any size reduction */
191 if (compctx->flags & COMP_F_ADAPTIVE)
192 {
193 lzo_adaptive_compress_data(&compctx->wu.lzo.ac, buf->len, work.len);
194 }
195 }
196
197 /* did compression save us anything ? */
198 if (compressed && work.len < buf->len)
199 {
200 uint8_t *header = buf_prepend(&work, 1);
201 *header = LZO_COMPRESS_BYTE;
202 *buf = work;
203 }
204 else
205 {
206 uint8_t *header = buf_prepend(buf, 1);
207 *header = NO_COMPRESS_BYTE;
208 }
209 }
210
211 static void
lzo_decompress(struct buffer * buf,struct buffer work,struct compress_context * compctx,const struct frame * frame)212 lzo_decompress(struct buffer *buf, struct buffer work,
213 struct compress_context *compctx,
214 const struct frame *frame)
215 {
216 lzo_uint zlen = EXPANDED_SIZE(frame);
217 int err;
218 uint8_t c; /* flag indicating whether or not our peer compressed */
219
220 if (buf->len <= 0)
221 {
222 return;
223 }
224
225 ASSERT(buf_init(&work, FRAME_HEADROOM(frame)));
226
227 c = *BPTR(buf);
228 ASSERT(buf_advance(buf, 1));
229
230 if (c == LZO_COMPRESS_BYTE) /* packet was compressed */
231 {
232 ASSERT(buf_safe(&work, zlen));
233 err = LZO_DECOMPRESS(BPTR(buf), BLEN(buf), BPTR(&work), &zlen,
234 compctx->wu.lzo.wmem);
235 if (err != LZO_E_OK)
236 {
237 dmsg(D_COMP_ERRORS, "LZO decompression error: %d", err);
238 buf->len = 0;
239 return;
240 }
241
242 ASSERT(buf_safe(&work, zlen));
243 work.len = zlen;
244
245 dmsg(D_COMP, "LZO decompress %d -> %d", buf->len, work.len);
246 compctx->pre_decompress += buf->len;
247 compctx->post_decompress += work.len;
248
249 *buf = work;
250 }
251 else if (c == NO_COMPRESS_BYTE) /* packet was not compressed */
252 {
253 }
254 else
255 {
256 dmsg(D_COMP_ERRORS, "Bad LZO decompression header byte: %d", c);
257 buf->len = 0;
258 }
259 }
260
261 const struct compress_alg lzo_alg = {
262 "lzo",
263 lzo_compress_init,
264 lzo_compress_uninit,
265 lzo_compress,
266 lzo_decompress
267 };
268
269 #else /* if defined(ENABLE_LZO) */
270 static void
dummy(void)271 dummy(void)
272 {
273 }
274 #endif /* ENABLE_LZO */
275