xref: /openbmc/linux/net/core/lwtunnel.c (revision 8bda81a4)
12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
2499a2425SRoopa Prabhu /*
3499a2425SRoopa Prabhu  * lwtunnel	Infrastructure for light weight tunnels like mpls
4499a2425SRoopa Prabhu  *
5499a2425SRoopa Prabhu  * Authors:	Roopa Prabhu, <roopa@cumulusnetworks.com>
6499a2425SRoopa Prabhu  */
7499a2425SRoopa Prabhu 
8499a2425SRoopa Prabhu #include <linux/capability.h>
9499a2425SRoopa Prabhu #include <linux/module.h>
10499a2425SRoopa Prabhu #include <linux/types.h>
11499a2425SRoopa Prabhu #include <linux/kernel.h>
12499a2425SRoopa Prabhu #include <linux/slab.h>
13499a2425SRoopa Prabhu #include <linux/uaccess.h>
14499a2425SRoopa Prabhu #include <linux/skbuff.h>
15499a2425SRoopa Prabhu #include <linux/netdevice.h>
16499a2425SRoopa Prabhu #include <linux/lwtunnel.h>
17499a2425SRoopa Prabhu #include <linux/in.h>
18499a2425SRoopa Prabhu #include <linux/init.h>
19499a2425SRoopa Prabhu #include <linux/err.h>
20499a2425SRoopa Prabhu 
21499a2425SRoopa Prabhu #include <net/lwtunnel.h>
22499a2425SRoopa Prabhu #include <net/rtnetlink.h>
23ffce4196SRoopa Prabhu #include <net/ip6_fib.h>
243c618c1dSDavid Ahern #include <net/rtnh.h>
25499a2425SRoopa Prabhu 
267a3f5b0dSRyoga Saito DEFINE_STATIC_KEY_FALSE(nf_hooks_lwtunnel_enabled);
277a3f5b0dSRyoga Saito EXPORT_SYMBOL_GPL(nf_hooks_lwtunnel_enabled);
287a3f5b0dSRyoga Saito 
29745041e2SRobert Shearman #ifdef CONFIG_MODULES
30745041e2SRobert Shearman 
31745041e2SRobert Shearman static const char *lwtunnel_encap_str(enum lwtunnel_encap_types encap_type)
32745041e2SRobert Shearman {
33745041e2SRobert Shearman 	/* Only lwt encaps implemented without using an interface for
34745041e2SRobert Shearman 	 * the encap need to return a string here.
35745041e2SRobert Shearman 	 */
36745041e2SRobert Shearman 	switch (encap_type) {
37745041e2SRobert Shearman 	case LWTUNNEL_ENCAP_MPLS:
38745041e2SRobert Shearman 		return "MPLS";
39745041e2SRobert Shearman 	case LWTUNNEL_ENCAP_ILA:
40745041e2SRobert Shearman 		return "ILA";
416c8702c6SDavid Lebrun 	case LWTUNNEL_ENCAP_SEG6:
426c8702c6SDavid Lebrun 		return "SEG6";
433a0af8fdSThomas Graf 	case LWTUNNEL_ENCAP_BPF:
443a0af8fdSThomas Graf 		return "BPF";
45d1df6fd8SDavid Lebrun 	case LWTUNNEL_ENCAP_SEG6_LOCAL:
46d1df6fd8SDavid Lebrun 		return "SEG6LOCAL";
47a7a29f9cSAlexander Aring 	case LWTUNNEL_ENCAP_RPL:
48a7a29f9cSAlexander Aring 		return "RPL";
493edede08SJustin Iurman 	case LWTUNNEL_ENCAP_IOAM6:
503edede08SJustin Iurman 		return "IOAM6";
51745041e2SRobert Shearman 	case LWTUNNEL_ENCAP_IP6:
52745041e2SRobert Shearman 	case LWTUNNEL_ENCAP_IP:
53745041e2SRobert Shearman 	case LWTUNNEL_ENCAP_NONE:
54745041e2SRobert Shearman 	case __LWTUNNEL_ENCAP_MAX:
55745041e2SRobert Shearman 		/* should not have got here */
56745041e2SRobert Shearman 		WARN_ON(1);
57745041e2SRobert Shearman 		break;
58745041e2SRobert Shearman 	}
59745041e2SRobert Shearman 	return NULL;
60745041e2SRobert Shearman }
61745041e2SRobert Shearman 
62745041e2SRobert Shearman #endif /* CONFIG_MODULES */
63745041e2SRobert Shearman 
64499a2425SRoopa Prabhu struct lwtunnel_state *lwtunnel_state_alloc(int encap_len)
65499a2425SRoopa Prabhu {
66499a2425SRoopa Prabhu 	struct lwtunnel_state *lws;
67499a2425SRoopa Prabhu 
68499a2425SRoopa Prabhu 	lws = kzalloc(sizeof(*lws) + encap_len, GFP_ATOMIC);
69499a2425SRoopa Prabhu 
70499a2425SRoopa Prabhu 	return lws;
71499a2425SRoopa Prabhu }
7208bd10ffSRoopa Prabhu EXPORT_SYMBOL_GPL(lwtunnel_state_alloc);
73499a2425SRoopa Prabhu 
7492a99bf3SThomas Graf static const struct lwtunnel_encap_ops __rcu *
75499a2425SRoopa Prabhu 		lwtun_encaps[LWTUNNEL_ENCAP_MAX + 1] __read_mostly;
76499a2425SRoopa Prabhu 
77499a2425SRoopa Prabhu int lwtunnel_encap_add_ops(const struct lwtunnel_encap_ops *ops,
78499a2425SRoopa Prabhu 			   unsigned int num)
79499a2425SRoopa Prabhu {
80499a2425SRoopa Prabhu 	if (num > LWTUNNEL_ENCAP_MAX)
81499a2425SRoopa Prabhu 		return -ERANGE;
82499a2425SRoopa Prabhu 
83499a2425SRoopa Prabhu 	return !cmpxchg((const struct lwtunnel_encap_ops **)
84499a2425SRoopa Prabhu 			&lwtun_encaps[num],
85499a2425SRoopa Prabhu 			NULL, ops) ? 0 : -1;
86499a2425SRoopa Prabhu }
8708bd10ffSRoopa Prabhu EXPORT_SYMBOL_GPL(lwtunnel_encap_add_ops);
88499a2425SRoopa Prabhu 
89499a2425SRoopa Prabhu int lwtunnel_encap_del_ops(const struct lwtunnel_encap_ops *ops,
90499a2425SRoopa Prabhu 			   unsigned int encap_type)
91499a2425SRoopa Prabhu {
92499a2425SRoopa Prabhu 	int ret;
93499a2425SRoopa Prabhu 
94499a2425SRoopa Prabhu 	if (encap_type == LWTUNNEL_ENCAP_NONE ||
95499a2425SRoopa Prabhu 	    encap_type > LWTUNNEL_ENCAP_MAX)
96499a2425SRoopa Prabhu 		return -ERANGE;
97499a2425SRoopa Prabhu 
98499a2425SRoopa Prabhu 	ret = (cmpxchg((const struct lwtunnel_encap_ops **)
99499a2425SRoopa Prabhu 		       &lwtun_encaps[encap_type],
100499a2425SRoopa Prabhu 		       ops, NULL) == ops) ? 0 : -1;
101499a2425SRoopa Prabhu 
102499a2425SRoopa Prabhu 	synchronize_net();
103499a2425SRoopa Prabhu 
104499a2425SRoopa Prabhu 	return ret;
105499a2425SRoopa Prabhu }
10608bd10ffSRoopa Prabhu EXPORT_SYMBOL_GPL(lwtunnel_encap_del_ops);
107499a2425SRoopa Prabhu 
108faee6769SAlexander Aring int lwtunnel_build_state(struct net *net, u16 encap_type,
109127eb7cdSTom Herbert 			 struct nlattr *encap, unsigned int family,
1109ae28727SDavid Ahern 			 const void *cfg, struct lwtunnel_state **lws,
1119ae28727SDavid Ahern 			 struct netlink_ext_ack *extack)
112499a2425SRoopa Prabhu {
113499a2425SRoopa Prabhu 	const struct lwtunnel_encap_ops *ops;
1149ae28727SDavid Ahern 	bool found = false;
115499a2425SRoopa Prabhu 	int ret = -EINVAL;
116499a2425SRoopa Prabhu 
117499a2425SRoopa Prabhu 	if (encap_type == LWTUNNEL_ENCAP_NONE ||
1189ae28727SDavid Ahern 	    encap_type > LWTUNNEL_ENCAP_MAX) {
1199ae28727SDavid Ahern 		NL_SET_ERR_MSG_ATTR(extack, encap,
1209ae28727SDavid Ahern 				    "Unknown LWT encapsulation type");
121499a2425SRoopa Prabhu 		return ret;
1229ae28727SDavid Ahern 	}
123499a2425SRoopa Prabhu 
124499a2425SRoopa Prabhu 	ret = -EOPNOTSUPP;
125499a2425SRoopa Prabhu 	rcu_read_lock();
126499a2425SRoopa Prabhu 	ops = rcu_dereference(lwtun_encaps[encap_type]);
1273d25eabbSwenxu 	if (likely(ops && ops->build_state && try_module_get(ops->owner)))
1289ae28727SDavid Ahern 		found = true;
1293d25eabbSwenxu 	rcu_read_unlock();
1303d25eabbSwenxu 
1313d25eabbSwenxu 	if (found) {
132faee6769SAlexander Aring 		ret = ops->build_state(net, encap, family, cfg, lws, extack);
13385c81401SRobert Shearman 		if (ret)
13485c81401SRobert Shearman 			module_put(ops->owner);
1353d25eabbSwenxu 	} else {
1369ae28727SDavid Ahern 		/* don't rely on -EOPNOTSUPP to detect match as build_state
1379ae28727SDavid Ahern 		 * handlers could return it
1389ae28727SDavid Ahern 		 */
1399ae28727SDavid Ahern 		NL_SET_ERR_MSG_ATTR(extack, encap,
1409ae28727SDavid Ahern 				    "LWT encapsulation type not supported");
1419ae28727SDavid Ahern 	}
1429ae28727SDavid Ahern 
143499a2425SRoopa Prabhu 	return ret;
144499a2425SRoopa Prabhu }
14508bd10ffSRoopa Prabhu EXPORT_SYMBOL_GPL(lwtunnel_build_state);
146499a2425SRoopa Prabhu 
147c255bd68SDavid Ahern int lwtunnel_valid_encap_type(u16 encap_type, struct netlink_ext_ack *extack)
1489ed59592SDavid Ahern {
1499ed59592SDavid Ahern 	const struct lwtunnel_encap_ops *ops;
1509ed59592SDavid Ahern 	int ret = -EINVAL;
1519ed59592SDavid Ahern 
1529ed59592SDavid Ahern 	if (encap_type == LWTUNNEL_ENCAP_NONE ||
153c255bd68SDavid Ahern 	    encap_type > LWTUNNEL_ENCAP_MAX) {
154c255bd68SDavid Ahern 		NL_SET_ERR_MSG(extack, "Unknown lwt encapsulation type");
1559ed59592SDavid Ahern 		return ret;
156c255bd68SDavid Ahern 	}
1579ed59592SDavid Ahern 
1589ed59592SDavid Ahern 	rcu_read_lock();
1599ed59592SDavid Ahern 	ops = rcu_dereference(lwtun_encaps[encap_type]);
1609ed59592SDavid Ahern 	rcu_read_unlock();
1619ed59592SDavid Ahern #ifdef CONFIG_MODULES
1629ed59592SDavid Ahern 	if (!ops) {
1639ed59592SDavid Ahern 		const char *encap_type_str = lwtunnel_encap_str(encap_type);
1649ed59592SDavid Ahern 
1659ed59592SDavid Ahern 		if (encap_type_str) {
1669ed59592SDavid Ahern 			__rtnl_unlock();
1679ed59592SDavid Ahern 			request_module("rtnl-lwt-%s", encap_type_str);
1689ed59592SDavid Ahern 			rtnl_lock();
1699ed59592SDavid Ahern 
1709ed59592SDavid Ahern 			rcu_read_lock();
1719ed59592SDavid Ahern 			ops = rcu_dereference(lwtun_encaps[encap_type]);
1729ed59592SDavid Ahern 			rcu_read_unlock();
1739ed59592SDavid Ahern 		}
1749ed59592SDavid Ahern 	}
1759ed59592SDavid Ahern #endif
176c255bd68SDavid Ahern 	ret = ops ? 0 : -EOPNOTSUPP;
177c255bd68SDavid Ahern 	if (ret < 0)
178c255bd68SDavid Ahern 		NL_SET_ERR_MSG(extack, "lwt encapsulation type not supported");
179c255bd68SDavid Ahern 
180c255bd68SDavid Ahern 	return ret;
1819ed59592SDavid Ahern }
18208bd10ffSRoopa Prabhu EXPORT_SYMBOL_GPL(lwtunnel_valid_encap_type);
1839ed59592SDavid Ahern 
184c255bd68SDavid Ahern int lwtunnel_valid_encap_type_attr(struct nlattr *attr, int remaining,
185c255bd68SDavid Ahern 				   struct netlink_ext_ack *extack)
1869ed59592SDavid Ahern {
1879ed59592SDavid Ahern 	struct rtnexthop *rtnh = (struct rtnexthop *)attr;
1889ed59592SDavid Ahern 	struct nlattr *nla_entype;
1899ed59592SDavid Ahern 	struct nlattr *attrs;
1909ed59592SDavid Ahern 	u16 encap_type;
1919ed59592SDavid Ahern 	int attrlen;
1929ed59592SDavid Ahern 
1939ed59592SDavid Ahern 	while (rtnh_ok(rtnh, remaining)) {
1949ed59592SDavid Ahern 		attrlen = rtnh_attrlen(rtnh);
1959ed59592SDavid Ahern 		if (attrlen > 0) {
1969ed59592SDavid Ahern 			attrs = rtnh_attrs(rtnh);
1979ed59592SDavid Ahern 			nla_entype = nla_find(attrs, attrlen, RTA_ENCAP_TYPE);
1989ed59592SDavid Ahern 
1999ed59592SDavid Ahern 			if (nla_entype) {
200*8bda81a4SDavid Ahern 				if (nla_len(nla_entype) < sizeof(u16)) {
201*8bda81a4SDavid Ahern 					NL_SET_ERR_MSG(extack, "Invalid RTA_ENCAP_TYPE");
202*8bda81a4SDavid Ahern 					return -EINVAL;
203*8bda81a4SDavid Ahern 				}
2049ed59592SDavid Ahern 				encap_type = nla_get_u16(nla_entype);
2059ed59592SDavid Ahern 
206c255bd68SDavid Ahern 				if (lwtunnel_valid_encap_type(encap_type,
207c255bd68SDavid Ahern 							      extack) != 0)
2089ed59592SDavid Ahern 					return -EOPNOTSUPP;
2099ed59592SDavid Ahern 			}
2109ed59592SDavid Ahern 		}
2119ed59592SDavid Ahern 		rtnh = rtnh_next(rtnh, &remaining);
2129ed59592SDavid Ahern 	}
2139ed59592SDavid Ahern 
2149ed59592SDavid Ahern 	return 0;
2159ed59592SDavid Ahern }
21608bd10ffSRoopa Prabhu EXPORT_SYMBOL_GPL(lwtunnel_valid_encap_type_attr);
2179ed59592SDavid Ahern 
2181104d9baSTom Herbert void lwtstate_free(struct lwtunnel_state *lws)
2191104d9baSTom Herbert {
2201104d9baSTom Herbert 	const struct lwtunnel_encap_ops *ops = lwtun_encaps[lws->type];
2211104d9baSTom Herbert 
2221104d9baSTom Herbert 	if (ops->destroy_state) {
2231104d9baSTom Herbert 		ops->destroy_state(lws);
2241104d9baSTom Herbert 		kfree_rcu(lws, rcu);
2251104d9baSTom Herbert 	} else {
2261104d9baSTom Herbert 		kfree(lws);
2271104d9baSTom Herbert 	}
22885c81401SRobert Shearman 	module_put(ops->owner);
2291104d9baSTom Herbert }
23008bd10ffSRoopa Prabhu EXPORT_SYMBOL_GPL(lwtstate_free);
2311104d9baSTom Herbert 
232ffa8ce54SDavid Ahern int lwtunnel_fill_encap(struct sk_buff *skb, struct lwtunnel_state *lwtstate,
233ffa8ce54SDavid Ahern 			int encap_attr, int encap_type_attr)
234499a2425SRoopa Prabhu {
235499a2425SRoopa Prabhu 	const struct lwtunnel_encap_ops *ops;
236499a2425SRoopa Prabhu 	struct nlattr *nest;
23739f37095SDan Carpenter 	int ret;
238499a2425SRoopa Prabhu 
239499a2425SRoopa Prabhu 	if (!lwtstate)
240499a2425SRoopa Prabhu 		return 0;
241499a2425SRoopa Prabhu 
242499a2425SRoopa Prabhu 	if (lwtstate->type == LWTUNNEL_ENCAP_NONE ||
243499a2425SRoopa Prabhu 	    lwtstate->type > LWTUNNEL_ENCAP_MAX)
244499a2425SRoopa Prabhu 		return 0;
245499a2425SRoopa Prabhu 
246ae0be8deSMichal Kubecek 	nest = nla_nest_start_noflag(skb, encap_attr);
247a50fe0ffSPan Bian 	if (!nest)
24839f37095SDan Carpenter 		return -EMSGSIZE;
24939f37095SDan Carpenter 
25039f37095SDan Carpenter 	ret = -EOPNOTSUPP;
251499a2425SRoopa Prabhu 	rcu_read_lock();
252499a2425SRoopa Prabhu 	ops = rcu_dereference(lwtun_encaps[lwtstate->type]);
253499a2425SRoopa Prabhu 	if (likely(ops && ops->fill_encap))
254499a2425SRoopa Prabhu 		ret = ops->fill_encap(skb, lwtstate);
255499a2425SRoopa Prabhu 	rcu_read_unlock();
256499a2425SRoopa Prabhu 
257499a2425SRoopa Prabhu 	if (ret)
258499a2425SRoopa Prabhu 		goto nla_put_failure;
259499a2425SRoopa Prabhu 	nla_nest_end(skb, nest);
260ffa8ce54SDavid Ahern 	ret = nla_put_u16(skb, encap_type_attr, lwtstate->type);
261499a2425SRoopa Prabhu 	if (ret)
262499a2425SRoopa Prabhu 		goto nla_put_failure;
263499a2425SRoopa Prabhu 
264499a2425SRoopa Prabhu 	return 0;
265499a2425SRoopa Prabhu 
266499a2425SRoopa Prabhu nla_put_failure:
267499a2425SRoopa Prabhu 	nla_nest_cancel(skb, nest);
268499a2425SRoopa Prabhu 
269499a2425SRoopa Prabhu 	return (ret == -EOPNOTSUPP ? 0 : ret);
270499a2425SRoopa Prabhu }
27108bd10ffSRoopa Prabhu EXPORT_SYMBOL_GPL(lwtunnel_fill_encap);
272499a2425SRoopa Prabhu 
273499a2425SRoopa Prabhu int lwtunnel_get_encap_size(struct lwtunnel_state *lwtstate)
274499a2425SRoopa Prabhu {
275499a2425SRoopa Prabhu 	const struct lwtunnel_encap_ops *ops;
276499a2425SRoopa Prabhu 	int ret = 0;
277499a2425SRoopa Prabhu 
278499a2425SRoopa Prabhu 	if (!lwtstate)
279499a2425SRoopa Prabhu 		return 0;
280499a2425SRoopa Prabhu 
281499a2425SRoopa Prabhu 	if (lwtstate->type == LWTUNNEL_ENCAP_NONE ||
282499a2425SRoopa Prabhu 	    lwtstate->type > LWTUNNEL_ENCAP_MAX)
283499a2425SRoopa Prabhu 		return 0;
284499a2425SRoopa Prabhu 
285499a2425SRoopa Prabhu 	rcu_read_lock();
286499a2425SRoopa Prabhu 	ops = rcu_dereference(lwtun_encaps[lwtstate->type]);
287499a2425SRoopa Prabhu 	if (likely(ops && ops->get_encap_size))
288499a2425SRoopa Prabhu 		ret = nla_total_size(ops->get_encap_size(lwtstate));
289499a2425SRoopa Prabhu 	rcu_read_unlock();
290499a2425SRoopa Prabhu 
291499a2425SRoopa Prabhu 	return ret;
292499a2425SRoopa Prabhu }
29308bd10ffSRoopa Prabhu EXPORT_SYMBOL_GPL(lwtunnel_get_encap_size);
294499a2425SRoopa Prabhu 
295499a2425SRoopa Prabhu int lwtunnel_cmp_encap(struct lwtunnel_state *a, struct lwtunnel_state *b)
296499a2425SRoopa Prabhu {
297499a2425SRoopa Prabhu 	const struct lwtunnel_encap_ops *ops;
298499a2425SRoopa Prabhu 	int ret = 0;
299499a2425SRoopa Prabhu 
300499a2425SRoopa Prabhu 	if (!a && !b)
301499a2425SRoopa Prabhu 		return 0;
302499a2425SRoopa Prabhu 
303499a2425SRoopa Prabhu 	if (!a || !b)
304499a2425SRoopa Prabhu 		return 1;
305499a2425SRoopa Prabhu 
306499a2425SRoopa Prabhu 	if (a->type != b->type)
307499a2425SRoopa Prabhu 		return 1;
308499a2425SRoopa Prabhu 
309499a2425SRoopa Prabhu 	if (a->type == LWTUNNEL_ENCAP_NONE ||
310499a2425SRoopa Prabhu 	    a->type > LWTUNNEL_ENCAP_MAX)
311499a2425SRoopa Prabhu 		return 0;
312499a2425SRoopa Prabhu 
313499a2425SRoopa Prabhu 	rcu_read_lock();
314499a2425SRoopa Prabhu 	ops = rcu_dereference(lwtun_encaps[a->type]);
315499a2425SRoopa Prabhu 	if (likely(ops && ops->cmp_encap))
316499a2425SRoopa Prabhu 		ret = ops->cmp_encap(a, b);
317499a2425SRoopa Prabhu 	rcu_read_unlock();
318499a2425SRoopa Prabhu 
319499a2425SRoopa Prabhu 	return ret;
320499a2425SRoopa Prabhu }
32108bd10ffSRoopa Prabhu EXPORT_SYMBOL_GPL(lwtunnel_cmp_encap);
322ffce4196SRoopa Prabhu 
323ede2059dSEric W. Biederman int lwtunnel_output(struct net *net, struct sock *sk, struct sk_buff *skb)
324ffce4196SRoopa Prabhu {
32561adedf3SJiri Benc 	struct dst_entry *dst = skb_dst(skb);
326ffce4196SRoopa Prabhu 	const struct lwtunnel_encap_ops *ops;
32761adedf3SJiri Benc 	struct lwtunnel_state *lwtstate;
328ffce4196SRoopa Prabhu 	int ret = -EINVAL;
329ffce4196SRoopa Prabhu 
33061adedf3SJiri Benc 	if (!dst)
331ffce4196SRoopa Prabhu 		goto drop;
33261adedf3SJiri Benc 	lwtstate = dst->lwtstate;
333ffce4196SRoopa Prabhu 
334ffce4196SRoopa Prabhu 	if (lwtstate->type == LWTUNNEL_ENCAP_NONE ||
335ffce4196SRoopa Prabhu 	    lwtstate->type > LWTUNNEL_ENCAP_MAX)
336ffce4196SRoopa Prabhu 		return 0;
337ffce4196SRoopa Prabhu 
338ffce4196SRoopa Prabhu 	ret = -EOPNOTSUPP;
339ffce4196SRoopa Prabhu 	rcu_read_lock();
340ffce4196SRoopa Prabhu 	ops = rcu_dereference(lwtun_encaps[lwtstate->type]);
341ffce4196SRoopa Prabhu 	if (likely(ops && ops->output))
342ede2059dSEric W. Biederman 		ret = ops->output(net, sk, skb);
343ffce4196SRoopa Prabhu 	rcu_read_unlock();
344ffce4196SRoopa Prabhu 
345ffce4196SRoopa Prabhu 	if (ret == -EOPNOTSUPP)
346ffce4196SRoopa Prabhu 		goto drop;
347ffce4196SRoopa Prabhu 
348ffce4196SRoopa Prabhu 	return ret;
349ffce4196SRoopa Prabhu 
350ffce4196SRoopa Prabhu drop:
351e11f40b9SDan Carpenter 	kfree_skb(skb);
352ffce4196SRoopa Prabhu 
353ffce4196SRoopa Prabhu 	return ret;
354ffce4196SRoopa Prabhu }
35508bd10ffSRoopa Prabhu EXPORT_SYMBOL_GPL(lwtunnel_output);
35625368623STom Herbert 
35714972cbdSRoopa Prabhu int lwtunnel_xmit(struct sk_buff *skb)
35814972cbdSRoopa Prabhu {
35914972cbdSRoopa Prabhu 	struct dst_entry *dst = skb_dst(skb);
36014972cbdSRoopa Prabhu 	const struct lwtunnel_encap_ops *ops;
36114972cbdSRoopa Prabhu 	struct lwtunnel_state *lwtstate;
36214972cbdSRoopa Prabhu 	int ret = -EINVAL;
36314972cbdSRoopa Prabhu 
36414972cbdSRoopa Prabhu 	if (!dst)
36514972cbdSRoopa Prabhu 		goto drop;
36614972cbdSRoopa Prabhu 
36714972cbdSRoopa Prabhu 	lwtstate = dst->lwtstate;
36814972cbdSRoopa Prabhu 
36914972cbdSRoopa Prabhu 	if (lwtstate->type == LWTUNNEL_ENCAP_NONE ||
37014972cbdSRoopa Prabhu 	    lwtstate->type > LWTUNNEL_ENCAP_MAX)
37114972cbdSRoopa Prabhu 		return 0;
37214972cbdSRoopa Prabhu 
37314972cbdSRoopa Prabhu 	ret = -EOPNOTSUPP;
37414972cbdSRoopa Prabhu 	rcu_read_lock();
37514972cbdSRoopa Prabhu 	ops = rcu_dereference(lwtun_encaps[lwtstate->type]);
37614972cbdSRoopa Prabhu 	if (likely(ops && ops->xmit))
37714972cbdSRoopa Prabhu 		ret = ops->xmit(skb);
37814972cbdSRoopa Prabhu 	rcu_read_unlock();
37914972cbdSRoopa Prabhu 
38014972cbdSRoopa Prabhu 	if (ret == -EOPNOTSUPP)
38114972cbdSRoopa Prabhu 		goto drop;
38214972cbdSRoopa Prabhu 
38314972cbdSRoopa Prabhu 	return ret;
38414972cbdSRoopa Prabhu 
38514972cbdSRoopa Prabhu drop:
38614972cbdSRoopa Prabhu 	kfree_skb(skb);
38714972cbdSRoopa Prabhu 
38814972cbdSRoopa Prabhu 	return ret;
38914972cbdSRoopa Prabhu }
39008bd10ffSRoopa Prabhu EXPORT_SYMBOL_GPL(lwtunnel_xmit);
39114972cbdSRoopa Prabhu 
39261adedf3SJiri Benc int lwtunnel_input(struct sk_buff *skb)
39325368623STom Herbert {
39461adedf3SJiri Benc 	struct dst_entry *dst = skb_dst(skb);
39525368623STom Herbert 	const struct lwtunnel_encap_ops *ops;
39661adedf3SJiri Benc 	struct lwtunnel_state *lwtstate;
39725368623STom Herbert 	int ret = -EINVAL;
39825368623STom Herbert 
39961adedf3SJiri Benc 	if (!dst)
40025368623STom Herbert 		goto drop;
40161adedf3SJiri Benc 	lwtstate = dst->lwtstate;
40225368623STom Herbert 
40325368623STom Herbert 	if (lwtstate->type == LWTUNNEL_ENCAP_NONE ||
40425368623STom Herbert 	    lwtstate->type > LWTUNNEL_ENCAP_MAX)
40525368623STom Herbert 		return 0;
40625368623STom Herbert 
40725368623STom Herbert 	ret = -EOPNOTSUPP;
40825368623STom Herbert 	rcu_read_lock();
40925368623STom Herbert 	ops = rcu_dereference(lwtun_encaps[lwtstate->type]);
41025368623STom Herbert 	if (likely(ops && ops->input))
41125368623STom Herbert 		ret = ops->input(skb);
41225368623STom Herbert 	rcu_read_unlock();
41325368623STom Herbert 
41425368623STom Herbert 	if (ret == -EOPNOTSUPP)
41525368623STom Herbert 		goto drop;
41625368623STom Herbert 
41725368623STom Herbert 	return ret;
41825368623STom Herbert 
41925368623STom Herbert drop:
42025368623STom Herbert 	kfree_skb(skb);
42125368623STom Herbert 
42225368623STom Herbert 	return ret;
42325368623STom Herbert }
42408bd10ffSRoopa Prabhu EXPORT_SYMBOL_GPL(lwtunnel_input);
425