Contributors: 15
Author Tokens Token Proportion Commits Commit Proportion
Herbert Xu 1304 76.89% 16 47.06%
James Morris 243 14.33% 3 8.82%
Mitsuru Kanda 74 4.36% 1 2.94%
Sabrina Dubroca 24 1.42% 2 5.88%
Fernando Fernandez Mancera 11 0.65% 1 2.94%
Ian Campbell 10 0.59% 1 2.94%
Arnaldo Carvalho de Melo 6 0.35% 1 2.94%
Johannes Berg 5 0.29% 1 2.94%
Eric Dumazet 5 0.29% 2 5.88%
Jonathan Lemon 4 0.24% 1 2.94%
Lin Yun Sheng 4 0.24% 1 2.94%
Tejun Heo 2 0.12% 1 2.94%
Shan Wei 2 0.12% 1 2.94%
Thomas Gleixner 1 0.06% 1 2.94%
Ingo Oeser 1 0.06% 1 2.94%
Total 1696 34


// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * IP Payload Compression Protocol (IPComp) - RFC3173.
 *
 * Copyright (c) 2003 James Morris <jmorris@intercode.com.au>
 * Copyright (c) 2003-2025 Herbert Xu <herbert@gondor.apana.org.au>
 *
 * Todo:
 *   - Tunable compression parameters.
 *   - Compression stats.
 *   - Adaptive compression.
 */

#include <crypto/acompress.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/skbuff_ref.h>
#include <linux/slab.h>
#include <net/ipcomp.h>
#include <net/xfrm.h>

#define IPCOMP_SCRATCH_SIZE 65400

struct ipcomp_skb_cb {
	struct xfrm_skb_cb xfrm;
	struct acomp_req *req;
};

struct ipcomp_data {
	u16 threshold;
	struct crypto_acomp *tfm;
};

struct ipcomp_req_extra {
	struct xfrm_state *x;
	struct scatterlist sg[];
};

static inline struct ipcomp_skb_cb *ipcomp_cb(struct sk_buff *skb)
{
	struct ipcomp_skb_cb *cb = (void *)skb->cb;

	BUILD_BUG_ON(sizeof(*cb) > sizeof(skb->cb));
	return cb;
}

static int ipcomp_post_acomp(struct sk_buff *skb, int err, int hlen)
{
	struct acomp_req *req = ipcomp_cb(skb)->req;
	struct ipcomp_req_extra *extra;
	struct scatterlist *dsg;
	int len, dlen;

	if (unlikely(err))
		goto out_free_req;

	extra = acomp_request_extra(req);
	dsg = extra->sg;
	dlen = req->dlen;

	pskb_trim_unique(skb, 0);
	__skb_put(skb, hlen);

	/* Only update truesize on input. */
	if (!hlen)
		skb->truesize += dlen;
	skb->data_len = dlen;
	skb->len += dlen;

	do {
		skb_frag_t *frag;
		struct page *page;

		frag = skb_shinfo(skb)->frags + skb_shinfo(skb)->nr_frags;
		page = sg_page(dsg);
		dsg = sg_next(dsg);

		len = PAGE_SIZE;
		if (dlen < len)
			len = dlen;

		skb_frag_fill_page_desc(frag, page, 0, len);

		skb_shinfo(skb)->nr_frags++;
	} while ((dlen -= len));

	for (; dsg; dsg = sg_next(dsg))
		__free_page(sg_page(dsg));

out_free_req:
	acomp_request_free(req);
	return err;
}

static int ipcomp_input_done2(struct sk_buff *skb, int err)
{
	struct ip_comp_hdr *ipch = ip_comp_hdr(skb);
	const int plen = skb->len;

	skb->transport_header = skb->network_header + sizeof(*ipch);

	return ipcomp_post_acomp(skb, err, 0) ?:
	       skb->len < (plen + sizeof(ip_comp_hdr)) ? -EINVAL :
	       ipch->nexthdr;
}

static void ipcomp_input_done(void *data, int err)
{
	struct sk_buff *skb = data;

	xfrm_input_resume(skb, ipcomp_input_done2(skb, err));
}

static struct acomp_req *ipcomp_setup_req(struct xfrm_state *x,
					  struct sk_buff *skb, int minhead,
					  int dlen)
{
	const int dnfrags = min(MAX_SKB_FRAGS, 16);
	struct ipcomp_data *ipcd = x->data;
	struct ipcomp_req_extra *extra;
	struct scatterlist *sg, *dsg;
	const int plen = skb->len;
	struct crypto_acomp *tfm;
	struct acomp_req *req;
	int nfrags;
	int total;
	int err;
	int i;

	ipcomp_cb(skb)->req = NULL;

	do {
		struct sk_buff *trailer;

		if (skb->len > PAGE_SIZE) {
			if (skb_linearize_cow(skb))
				return ERR_PTR(-ENOMEM);
			nfrags = 1;
			break;
		}

		if (!skb_cloned(skb) && skb_headlen(skb) >= minhead) {
			if (!skb_is_nonlinear(skb)) {
				nfrags = 1;
				break;
			} else if (!skb_has_frag_list(skb)) {
				nfrags = skb_shinfo(skb)->nr_frags;
				nfrags++;
				break;
			}
		}

		nfrags = skb_cow_data(skb, skb_headlen(skb) < minhead ?
					   minhead - skb_headlen(skb) : 0,
				      &trailer);
		if (nfrags < 0)
			return ERR_PTR(nfrags);
	} while (0);

	tfm = ipcd->tfm;
	req = acomp_request_alloc_extra(
		tfm, sizeof(*extra) + sizeof(*sg) * (nfrags + dnfrags),
		GFP_ATOMIC);
	ipcomp_cb(skb)->req = req;
	if (!req)
		return ERR_PTR(-ENOMEM);

	extra = acomp_request_extra(req);
	extra->x = x;

	dsg = extra->sg;
	sg = dsg + dnfrags;
	sg_init_table(sg, nfrags);
	err = skb_to_sgvec(skb, sg, 0, plen);
	if (unlikely(err < 0))
		return ERR_PTR(err);

	sg_init_table(dsg, dnfrags);
	total = 0;
	for (i = 0; i < dnfrags && total < dlen; i++) {
		struct page *page;

		page = alloc_page(GFP_ATOMIC);
		if (!page)
			break;
		sg_set_page(dsg + i, page, PAGE_SIZE, 0);
		total += PAGE_SIZE;
	}
	if (!i)
		return ERR_PTR(-ENOMEM);
	sg_mark_end(dsg + i - 1);
	dlen = min(dlen, total);

	acomp_request_set_params(req, sg, dsg, plen, dlen);

	return req;
}

static int ipcomp_decompress(struct xfrm_state *x, struct sk_buff *skb)
{
	struct acomp_req *req;
	int err;

	req = ipcomp_setup_req(x, skb, 0, IPCOMP_SCRATCH_SIZE);
	err = PTR_ERR(req);
	if (IS_ERR(req))
		goto out;

	acomp_request_set_callback(req, 0, ipcomp_input_done, skb);
	err = crypto_acomp_decompress(req);
	if (err == -EINPROGRESS)
		return err;

out:
	return ipcomp_input_done2(skb, err);
}

int ipcomp_input(struct xfrm_state *x, struct sk_buff *skb)
{
	struct ip_comp_hdr *ipch __maybe_unused;

	if (!pskb_may_pull(skb, sizeof(*ipch)))
		return -EINVAL;

	skb->ip_summed = CHECKSUM_NONE;

	/* Remove ipcomp header and decompress original payload */
	__skb_pull(skb, sizeof(*ipch));

	return ipcomp_decompress(x, skb);
}
EXPORT_SYMBOL_GPL(ipcomp_input);

static int ipcomp_output_push(struct sk_buff *skb)
{
	skb_push(skb, -skb_network_offset(skb));
	return 0;
}

static int ipcomp_output_done2(struct xfrm_state *x, struct sk_buff *skb,
			       int err)
{
	struct ip_comp_hdr *ipch;

	err = ipcomp_post_acomp(skb, err, sizeof(*ipch));
	if (err)
		goto out_ok;

	/* Install ipcomp header, convert into ipcomp datagram. */
	ipch = ip_comp_hdr(skb);
	ipch->nexthdr = *skb_mac_header(skb);
	ipch->flags = 0;
	ipch->cpi = htons((u16 )ntohl(x->id.spi));
	*skb_mac_header(skb) = IPPROTO_COMP;
out_ok:
	return ipcomp_output_push(skb);
}

static void ipcomp_output_done(void *data, int err)
{
	struct ipcomp_req_extra *extra;
	struct sk_buff *skb = data;
	struct acomp_req *req;

	req = ipcomp_cb(skb)->req;
	extra = acomp_request_extra(req);

	xfrm_output_resume(skb_to_full_sk(skb), skb,
			   ipcomp_output_done2(extra->x, skb, err));
}

static int ipcomp_compress(struct xfrm_state *x, struct sk_buff *skb)
{
	struct ip_comp_hdr *ipch __maybe_unused;
	struct acomp_req *req;
	int err;

	req = ipcomp_setup_req(x, skb, sizeof(*ipch),
			       skb->len - sizeof(*ipch));
	err = PTR_ERR(req);
	if (IS_ERR(req))
		goto out;

	acomp_request_set_callback(req, 0, ipcomp_output_done, skb);
	err = crypto_acomp_compress(req);
	if (err == -EINPROGRESS)
		return err;

out:
	return ipcomp_output_done2(x, skb, err);
}

int ipcomp_output(struct xfrm_state *x, struct sk_buff *skb)
{
	struct ipcomp_data *ipcd = x->data;

	if (skb->len < ipcd->threshold) {
		/* Don't bother compressing */
		return ipcomp_output_push(skb);
	}

	return ipcomp_compress(x, skb);
}
EXPORT_SYMBOL_GPL(ipcomp_output);

static void ipcomp_free_data(struct ipcomp_data *ipcd)
{
	crypto_free_acomp(ipcd->tfm);
}

void ipcomp_destroy(struct xfrm_state *x)
{
	struct ipcomp_data *ipcd = x->data;
	if (!ipcd)
		return;
	ipcomp_free_data(ipcd);
	kfree(ipcd);
}
EXPORT_SYMBOL_GPL(ipcomp_destroy);

int ipcomp_init_state(struct xfrm_state *x, struct netlink_ext_ack *extack)
{
	int err;
	struct ipcomp_data *ipcd;
	struct xfrm_algo_desc *calg_desc;

	err = -EINVAL;
	if (!x->calg) {
		NL_SET_ERR_MSG(extack, "Missing required compression algorithm");
		goto out;
	}

	if (x->encap) {
		NL_SET_ERR_MSG(extack, "IPComp is not compatible with encapsulation");
		goto out;
	}

	err = -ENOMEM;
	ipcd = kzalloc(sizeof(*ipcd), GFP_KERNEL);
	if (!ipcd)
		goto out;

	ipcd->tfm = crypto_alloc_acomp(x->calg->alg_name, 0, 0);
	if (IS_ERR(ipcd->tfm))
		goto error;

	calg_desc = xfrm_calg_get_byname(x->calg->alg_name, 0);
	BUG_ON(!calg_desc);
	ipcd->threshold = calg_desc->uinfo.comp.threshold;
	x->data = ipcd;
	err = 0;
out:
	return err;

error:
	ipcomp_free_data(ipcd);
	kfree(ipcd);
	goto out;
}
EXPORT_SYMBOL_GPL(ipcomp_init_state);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("IP Payload Compression Protocol (IPComp) - RFC3173");
MODULE_AUTHOR("James Morris <jmorris@intercode.com.au>");