From be62fff9e402d9ecf042905d74455e49ae5e5560 Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Tue, 27 Jan 2015 01:23:44 +0000 Subject: [PATCH] Add LZO decompression support It doesn't look like we get to negotiate this. If the server is configured to do compression, it *will* compress packets it sends to us via ESP. So unless we want to eschew ESP entirely when compression is offered, we need at least a decompressor which is LGPL-compatible (unlike liblzo2). Thankfully. libavutil happens to have one that we can steal. It doesn't look that hard to do a compressor for it too. Signed-off-by: David Woodhouse --- Makefile.am | 2 +- esp.c | 31 +++++- lzo.c | 267 ++++++++++++++++++++++++++++++++++++++++++++++++++++ lzo.h | 86 +++++++++++++++++ 4 files changed, 381 insertions(+), 5 deletions(-) create mode 100644 lzo.c create mode 100644 lzo.h diff --git a/Makefile.am b/Makefile.am index 1cfc15bc..1977ef74 100644 --- a/Makefile.am +++ b/Makefile.am @@ -25,7 +25,7 @@ openconnect_LDADD = libopenconnect.la $(SSL_LIBS) $(LIBXML2_LIBS) $(LIBPROXY_LIB library_srcs = ssl.c http.c auth-common.c library.c compat.c lzs.c mainloop.c script.c ntlm.c digest.c lib_srcs_cisco = auth.c cstp.c dtls.c -lib_srcs_juniper = oncp.c +lib_srcs_juniper = oncp.c lzo.c lib_srcs_gnutls = gnutls.c gnutls_pkcs12.c gnutls_tpm.c lib_srcs_openssl = openssl.c openssl-pkcs11.c lib_srcs_win32 = tun-win32.c sspi.c diff --git a/esp.c b/esp.c index 80a416cf..16ce436d 100644 --- a/esp.c +++ b/esp.c @@ -25,6 +25,7 @@ #include #include "openconnect-internal.h" +#include "lzo.h" /* Eventually we're going to have to have more than one incoming ESP context at a time, to allow for the overlap period during a rekey. @@ -261,8 +262,8 @@ int esp_mainloop(struct openconnect_info *vpninfo, int *timeout) continue; } - if (pkt->data[len - 1] != 0x04 && pkt->data[len - 1] != 0x29) { - /* 0x05 is LZO compressed. */ + if (pkt->data[len - 1] != 0x04 && pkt->data[len - 1] != 0x29 && + pkt->data[len - 1] != 0x05) { vpn_progress(vpninfo, PRG_ERR, _("Received ESP packet with unrecognised payload type %02x\n"), pkt->data[len-1]); @@ -286,8 +287,30 @@ int esp_mainloop(struct openconnect_info *vpninfo, int *timeout) } continue; } - queue_packet(&vpninfo->incoming_queue, pkt); - vpninfo->dtls_pkt = NULL; + if (pkt->data[len - 1] == 0x05) { + struct pkt *newpkt = malloc(sizeof(*pkt) + vpninfo->ip_info.mtu + vpninfo->pkt_trailer); + int newlen = vpninfo->ip_info.mtu; + if (!newpkt) { + vpn_progress(vpninfo, PRG_ERR, + _("Failed to allocate memory to decrypt ESP packet\n")); + continue; + } + if (av_lzo1x_decode(newpkt->data, &newlen, + pkt->data, &pkt->len) || pkt->len) { + vpn_progress(vpninfo, PRG_ERR, + _("LZO decompression of ESP packet failed\n")); + free(newpkt); + continue; + } + newpkt->len = vpninfo->ip_info.mtu - newlen; + vpn_progress(vpninfo, PRG_TRACE, + _("LZO decompressed %d bytes into %d\n"), + len - 2 - pkt->data[len-2], newpkt->len); + queue_packet(&vpninfo->incoming_queue, newpkt); + } else { + queue_packet(&vpninfo->incoming_queue, pkt); + vpninfo->dtls_pkt = NULL; + } } if (vpninfo->dtls_state != DTLS_CONNECTED) diff --git a/lzo.c b/lzo.c new file mode 100644 index 00000000..ac95fb9b --- /dev/null +++ b/lzo.c @@ -0,0 +1,267 @@ +/* + * LZO 1x decompression + * Copyright (c) 2006 Reimar Doeffinger + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +//#include "avutil.h" +//#include "avassert.h" +//#include "common.h" +//#include "intreadwrite.h" +#include "lzo.h" + +/// Define if we may write up to 12 bytes beyond the output buffer. +#define OUTBUF_PADDED 1 +/// Define if we may read up to 8 bytes beyond the input buffer. +#define INBUF_PADDED 1 + +typedef struct LZOContext { + const uint8_t *in, *in_end; + uint8_t *out_start, *out, *out_end; + int error; +} LZOContext; + +/** + * @brief Reads one byte from the input buffer, avoiding an overrun. + * @return byte read + */ +static inline int get_byte(LZOContext *c) +{ + if (c->in < c->in_end) + return *c->in++; + c->error |= AV_LZO_INPUT_DEPLETED; + return 1; +} + +#ifdef INBUF_PADDED +#define GETB(c) (*(c).in++) +#else +#define GETB(c) get_byte(&(c)) +#endif + +/** + * @brief Decodes a length value in the coding used by lzo. + * @param x previous byte value + * @param mask bits used from x + * @return decoded length value + */ +static inline int get_len(LZOContext *c, int x, int mask) +{ + int cnt = x & mask; + if (!cnt) { + while (!(x = get_byte(c))) { + if (cnt >= 65535) { + c->error |= AV_LZO_ERROR; + break; + } + cnt += 255; + } + cnt += mask + x; + } + return cnt; +} + +/** + * @brief Copies bytes from input to output buffer with checking. + * @param cnt number of bytes to copy, must be >= 0 + */ +static inline void copy(LZOContext *c, int cnt) +{ + register const uint8_t *src = c->in; + register uint8_t *dst = c->out; + /* Should never happen */ + if (cnt < 0) { + c->error |= AV_LZO_ERROR; + return; + } + if (cnt > c->in_end - src) { + cnt = FFMAX(c->in_end - src, 0); + c->error |= AV_LZO_INPUT_DEPLETED; + } + if (cnt > c->out_end - dst) { + cnt = FFMAX(c->out_end - dst, 0); + c->error |= AV_LZO_OUTPUT_FULL; + } +#if defined(INBUF_PADDED) && defined(OUTBUF_PADDED) + AV_COPY32U(dst, src); + src += 4; + dst += 4; + cnt -= 4; + if (cnt > 0) +#endif + memcpy(dst, src, cnt); + c->in = src + cnt; + c->out = dst + cnt; +} + +/** + * @brief Copies previously decoded bytes to current position. + * @param back how many bytes back we start, must be > 0 + * @param cnt number of bytes to copy, must be > 0 + * + * cnt > back is valid, this will copy the bytes we just copied, + * thus creating a repeating pattern with a period length of back. + */ +static inline void copy_backptr(LZOContext *c, int back, int cnt) +{ + register uint8_t *dst = c->out; + if (cnt <= 0) { + c->error |= AV_LZO_ERROR; + return; + } + if (dst - c->out_start < back) { + c->error |= AV_LZO_INVALID_BACKPTR; + return; + } + if (cnt > c->out_end - dst) { + cnt = FFMAX(c->out_end - dst, 0); + c->error |= AV_LZO_OUTPUT_FULL; + } + av_memcpy_backptr(dst, back, cnt); + c->out = dst + cnt; +} + +int av_lzo1x_decode(void *out, int *outlen, const void *in, int *inlen) +{ + int state = 0; + int x; + LZOContext c; + if (*outlen <= 0 || *inlen <= 0) { + int res = 0; + if (*outlen <= 0) + res |= AV_LZO_OUTPUT_FULL; + if (*inlen <= 0) + res |= AV_LZO_INPUT_DEPLETED; + return res; + } + c.in = in; + c.in_end = (const uint8_t *)in + *inlen; + c.out = c.out_start = out; + c.out_end = (uint8_t *)out + *outlen; + c.error = 0; + x = GETB(c); + if (x > 17) { + copy(&c, x - 17); + x = GETB(c); + if (x < 16) + c.error |= AV_LZO_ERROR; + } + if (c.in > c.in_end) + c.error |= AV_LZO_INPUT_DEPLETED; + while (!c.error) { + int cnt, back; + if (x > 15) { + if (x > 63) { /* cccbbbnn BBBBBBBB */ + cnt = (x >> 5) - 1; + back = (GETB(c) << 3) + ((x >> 2) & 7) + 1; + } else if (x > 31) { /* 001ccccc (cccccccc...) bbbbbbnn BBBBBBBB */ + cnt = get_len(&c, x, 31); + x = GETB(c); + back = (GETB(c) << 6) + (x >> 2) + 1; + } else { /* 0001bccc (cccccccc...) bbbbbbnn BBBBBBBB */ + cnt = get_len(&c, x, 7); + back = (1 << 14) + ((x & 8) << 11); + x = GETB(c); + back += (GETB(c) << 6) + (x >> 2); + if (back == (1 << 14)) { + if (cnt != 1) + c.error |= AV_LZO_ERROR; + break; + } + } + } else if (!state) { /* 0000llll (llllllll...) { literal... } ( 0000bbnn BBBBBBBB ) */ + cnt = get_len(&c, x, 15); + copy(&c, cnt + 3); + x = GETB(c); + if (x > 15) + continue; + cnt = 1; + back = (1 << 11) + (GETB(c) << 2) + (x >> 2) + 1; + } else { /* 0000bbnn BBBBBBBB ) */ + cnt = 0; + back = (GETB(c) << 2) + (x >> 2) + 1; + } + copy_backptr(&c, back, cnt + 2); + state = + cnt = x & 3; + copy(&c, cnt); + x = GETB(c); + } + *inlen = c.in_end - c.in; + if (c.in > c.in_end) + *inlen = 0; + *outlen = c.out_end - c.out; + return c.error; +} + +#ifdef TEST +#include +#include +#include "log.h" +#define MAXSZ (10*1024*1024) + +/* Define one of these to 1 if you wish to benchmark liblzo + * instead of our native implementation. */ +#define BENCHMARK_LIBLZO_SAFE 0 +#define BENCHMARK_LIBLZO_UNSAFE 0 + +int main(int argc, char *argv[]) { + FILE *in = fopen(argv[1], "rb"); + int comp_level = argc > 2 ? atoi(argv[2]) : 0; + uint8_t *orig = av_malloc(MAXSZ + 16); + uint8_t *comp = av_malloc(2*MAXSZ + 16); + uint8_t *decomp = av_malloc(MAXSZ + 16); + size_t s = fread(orig, 1, MAXSZ, in); + lzo_uint clen = 0; + long tmp[LZO1X_MEM_COMPRESS]; + int inlen, outlen; + int i; + av_log_set_level(AV_LOG_DEBUG); + if (comp_level == 0) { + lzo1x_1_compress(orig, s, comp, &clen, tmp); + } else if (comp_level == 11) { + lzo1x_1_11_compress(orig, s, comp, &clen, tmp); + } else if (comp_level == 12) { + lzo1x_1_12_compress(orig, s, comp, &clen, tmp); + } else if (comp_level == 15) { + lzo1x_1_15_compress(orig, s, comp, &clen, tmp); + } else + lzo1x_999_compress(orig, s, comp, &clen, tmp); + for (i = 0; i < 300; i++) { +START_TIMER + inlen = clen; outlen = MAXSZ; +#if BENCHMARK_LIBLZO_SAFE + if (lzo1x_decompress_safe(comp, inlen, decomp, &outlen, NULL)) +#elif BENCHMARK_LIBLZO_UNSAFE + if (lzo1x_decompress(comp, inlen, decomp, &outlen, NULL)) +#else + if (av_lzo1x_decode(decomp, &outlen, comp, &inlen)) +#endif + av_log(NULL, AV_LOG_ERROR, "decompression error\n"); +STOP_TIMER("lzod") + } + if (memcmp(orig, decomp, s)) + av_log(NULL, AV_LOG_ERROR, "decompression incorrect\n"); + else + av_log(NULL, AV_LOG_ERROR, "decompression OK\n"); + fclose(in); + return 0; +} +#endif diff --git a/lzo.h b/lzo.h new file mode 100644 index 00000000..0ec245b0 --- /dev/null +++ b/lzo.h @@ -0,0 +1,86 @@ +/* + * LZO 1x decompression + * copyright (c) 2006 Reimar Doeffinger + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_LZO_H +#define AVUTIL_LZO_H + +/** + * @defgroup lavu_lzo LZO + * @ingroup lavu_crypto + * + * @{ + */ + +#include + +/** @name Error flags returned by av_lzo1x_decode + * @{ */ +/// end of the input buffer reached before decoding finished +#define AV_LZO_INPUT_DEPLETED 1 +/// decoded data did not fit into output buffer +#define AV_LZO_OUTPUT_FULL 2 +/// a reference to previously decoded data was wrong +#define AV_LZO_INVALID_BACKPTR 4 +/// a non-specific error in the compressed bitstream +#define AV_LZO_ERROR 8 +/** @} */ + +#define AV_LZO_INPUT_PADDING 8 +#define AV_LZO_OUTPUT_PADDING 12 + +/** + * @brief Decodes LZO 1x compressed data. + * @param out output buffer + * @param outlen size of output buffer, number of bytes left are returned here + * @param in input buffer + * @param inlen size of input buffer, number of bytes left are returned here + * @return 0 on success, otherwise a combination of the error flags above + * + * Make sure all buffers are appropriately padded, in must provide + * AV_LZO_INPUT_PADDING, out must provide AV_LZO_OUTPUT_PADDING additional bytes. + */ +int av_lzo1x_decode(void *out, int *outlen, const void *in, int *inlen); + +/** + * @} + */ + +#define FFMAX(x,y) ({ typeof(x) _x = (x) ; typeof(y) _y = (y); \ + _x > _y ? _x : _y; }) + +struct lzo_packed_uint32 { + uint32_t d; +} __attribute__((packed)); + +#define AV_COPY32U(dst,src) do { \ + ((struct lzo_packed_uint32 *)dst)->d = \ + ((struct lzo_packed_uint32 *)src)->d; \ + } while (0) + +static inline void av_memcpy_backptr(unsigned char *dst, int back, int cnt) +{ + while (cnt--) { + *dst = *(dst - back); + dst++; + } +} + +#endif /* AVUTIL_LZO_H */