1/*
2 * xfrm4_mode_beet.c - BEET mode encapsulation for IPv4.
3 *
4 * Copyright (c) 2006 Diego Beltrami <diego.beltrami@gmail.com>
5 *                    Miika Komu     <miika@iki.fi>
6 *                    Herbert Xu     <herbert@gondor.apana.org.au>
7 *                    Abhinav Pathak <abhinav.pathak@hiit.fi>
8 *                    Jeff Ahrenholz <ahrenholz@gmail.com>
9 */
10
11#include <linux/init.h>
12#include <linux/kernel.h>
13#include <linux/module.h>
14#include <linux/skbuff.h>
15#include <linux/stringify.h>
16#include <net/dst.h>
17#include <net/ip.h>
18#include <net/xfrm.h>
19
20/* Add encapsulation header.
21 *
22 * The top IP header will be constructed per draft-nikander-esp-beet-mode-06.txt.
23 * The following fields in it shall be filled in by x->type->output:
24 *      tot_len
25 *      check
26 *
27 * On exit, skb->h will be set to the start of the payload to be processed
28 * by x->type->output and skb->nh will be set to the top IP header.
29 */
30static int xfrm4_beet_output(struct xfrm_state *x, struct sk_buff *skb)
31{
32	struct iphdr *iph, *top_iph;
33	int hdrlen, optlen;
34
35	iph = ip_hdr(skb);
36	skb->transport_header = skb->network_header;
37
38	hdrlen = 0;
39	optlen = iph->ihl * 4 - sizeof(*iph);
40	if (unlikely(optlen))
41		hdrlen += IPV4_BEET_PHMAXLEN - (optlen & 4);
42
43	skb_push(skb, x->props.header_len - IPV4_BEET_PHMAXLEN + hdrlen);
44	skb_reset_network_header(skb);
45	top_iph = ip_hdr(skb);
46	skb->transport_header += sizeof(*iph) - hdrlen;
47
48	memmove(top_iph, iph, sizeof(*iph));
49	if (unlikely(optlen)) {
50		struct ip_beet_phdr *ph;
51
52		BUG_ON(optlen < 0);
53
54		ph = (struct ip_beet_phdr *)skb_transport_header(skb);
55		ph->padlen = 4 - (optlen & 4);
56		ph->hdrlen = optlen / 8;
57		ph->nexthdr = top_iph->protocol;
58		if (ph->padlen)
59			memset(ph + 1, IPOPT_NOP, ph->padlen);
60
61		top_iph->protocol = IPPROTO_BEETPH;
62		top_iph->ihl = sizeof(struct iphdr) / 4;
63	}
64
65	top_iph->saddr = x->props.saddr.a4;
66	top_iph->daddr = x->id.daddr.a4;
67
68	return 0;
69}
70
71static int xfrm4_beet_input(struct xfrm_state *x, struct sk_buff *skb)
72{
73	struct iphdr *iph = ip_hdr(skb);
74	int phlen = 0;
75	int optlen = 0;
76	u8 ph_nexthdr = 0;
77	int err = -EINVAL;
78
79	if (unlikely(iph->protocol == IPPROTO_BEETPH)) {
80		struct ip_beet_phdr *ph;
81
82		if (!pskb_may_pull(skb, sizeof(*ph)))
83			goto out;
84		ph = (struct ip_beet_phdr *)(ipip_hdr(skb) + 1);
85
86		phlen = sizeof(*ph) + ph->padlen;
87		optlen = ph->hdrlen * 8 + (IPV4_BEET_PHMAXLEN - phlen);
88		if (optlen < 0 || optlen & 3 || optlen > 250)
89			goto out;
90
91		if (!pskb_may_pull(skb, phlen + optlen))
92			goto out;
93		skb->len -= phlen + optlen;
94
95		ph_nexthdr = ph->nexthdr;
96	}
97
98	skb_set_network_header(skb, phlen - sizeof(*iph));
99	memmove(skb_network_header(skb), iph, sizeof(*iph));
100	skb_set_transport_header(skb, phlen + optlen);
101	skb->data = skb_transport_header(skb);
102
103	iph = ip_hdr(skb);
104	iph->ihl = (sizeof(*iph) + optlen) / 4;
105	iph->tot_len = htons(skb->len + iph->ihl * 4);
106	iph->daddr = x->sel.daddr.a4;
107	iph->saddr = x->sel.saddr.a4;
108	if (ph_nexthdr)
109		iph->protocol = ph_nexthdr;
110	iph->check = 0;
111	iph->check = ip_fast_csum(skb_network_header(skb), iph->ihl);
112	err = 0;
113out:
114	return err;
115}
116
117static struct xfrm_mode xfrm4_beet_mode = {
118	.input = xfrm4_beet_input,
119	.output = xfrm4_beet_output,
120	.owner = THIS_MODULE,
121	.encap = XFRM_MODE_BEET,
122};
123
124static int __init xfrm4_beet_init(void)
125{
126	return xfrm_register_mode(&xfrm4_beet_mode, AF_INET);
127}
128
129static void __exit xfrm4_beet_exit(void)
130{
131	int err;
132
133	err = xfrm_unregister_mode(&xfrm4_beet_mode, AF_INET);
134	BUG_ON(err);
135}
136
137module_init(xfrm4_beet_init);
138module_exit(xfrm4_beet_exit);
139MODULE_LICENSE("GPL");
140MODULE_ALIAS_XFRM_MODE(AF_INET, XFRM_MODE_BEET);
141