xref: /openbmc/linux/net/ipv6/seg6_local.c (revision 2874c5fd284268364ece81a7bd936f3c8168e567)
1*2874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
2d1df6fd8SDavid Lebrun /*
3d1df6fd8SDavid Lebrun  *  SR-IPv6 implementation
4d1df6fd8SDavid Lebrun  *
5004d4b27SMathieu Xhonneux  *  Authors:
6d1df6fd8SDavid Lebrun  *  David Lebrun <david.lebrun@uclouvain.be>
7004d4b27SMathieu Xhonneux  *  eBPF support: Mathieu Xhonneux <m.xhonneux@gmail.com>
8d1df6fd8SDavid Lebrun  */
9d1df6fd8SDavid Lebrun 
10d1df6fd8SDavid Lebrun #include <linux/types.h>
11d1df6fd8SDavid Lebrun #include <linux/skbuff.h>
12d1df6fd8SDavid Lebrun #include <linux/net.h>
13d1df6fd8SDavid Lebrun #include <linux/module.h>
14d1df6fd8SDavid Lebrun #include <net/ip.h>
15d1df6fd8SDavid Lebrun #include <net/lwtunnel.h>
16d1df6fd8SDavid Lebrun #include <net/netevent.h>
17d1df6fd8SDavid Lebrun #include <net/netns/generic.h>
18d1df6fd8SDavid Lebrun #include <net/ip6_fib.h>
19d1df6fd8SDavid Lebrun #include <net/route.h>
20d1df6fd8SDavid Lebrun #include <net/seg6.h>
21d1df6fd8SDavid Lebrun #include <linux/seg6.h>
22d1df6fd8SDavid Lebrun #include <linux/seg6_local.h>
23d1df6fd8SDavid Lebrun #include <net/addrconf.h>
24d1df6fd8SDavid Lebrun #include <net/ip6_route.h>
25d1df6fd8SDavid Lebrun #include <net/dst_cache.h>
26d1df6fd8SDavid Lebrun #ifdef CONFIG_IPV6_SEG6_HMAC
27d1df6fd8SDavid Lebrun #include <net/seg6_hmac.h>
28d1df6fd8SDavid Lebrun #endif
291c1e761eSMathieu Xhonneux #include <net/seg6_local.h>
30891ef8ddSDavid Lebrun #include <linux/etherdevice.h>
31004d4b27SMathieu Xhonneux #include <linux/bpf.h>
32d1df6fd8SDavid Lebrun 
33d1df6fd8SDavid Lebrun struct seg6_local_lwt;
34d1df6fd8SDavid Lebrun 
35d1df6fd8SDavid Lebrun struct seg6_action_desc {
36d1df6fd8SDavid Lebrun 	int action;
37d1df6fd8SDavid Lebrun 	unsigned long attrs;
38d1df6fd8SDavid Lebrun 	int (*input)(struct sk_buff *skb, struct seg6_local_lwt *slwt);
39d1df6fd8SDavid Lebrun 	int static_headroom;
40d1df6fd8SDavid Lebrun };
41d1df6fd8SDavid Lebrun 
42004d4b27SMathieu Xhonneux struct bpf_lwt_prog {
43004d4b27SMathieu Xhonneux 	struct bpf_prog *prog;
44004d4b27SMathieu Xhonneux 	char *name;
45004d4b27SMathieu Xhonneux };
46004d4b27SMathieu Xhonneux 
47d1df6fd8SDavid Lebrun struct seg6_local_lwt {
48d1df6fd8SDavid Lebrun 	int action;
49d1df6fd8SDavid Lebrun 	struct ipv6_sr_hdr *srh;
50d1df6fd8SDavid Lebrun 	int table;
51d1df6fd8SDavid Lebrun 	struct in_addr nh4;
52d1df6fd8SDavid Lebrun 	struct in6_addr nh6;
53d1df6fd8SDavid Lebrun 	int iif;
54d1df6fd8SDavid Lebrun 	int oif;
55004d4b27SMathieu Xhonneux 	struct bpf_lwt_prog bpf;
56d1df6fd8SDavid Lebrun 
57d1df6fd8SDavid Lebrun 	int headroom;
58d1df6fd8SDavid Lebrun 	struct seg6_action_desc *desc;
59d1df6fd8SDavid Lebrun };
60d1df6fd8SDavid Lebrun 
61d1df6fd8SDavid Lebrun static struct seg6_local_lwt *seg6_local_lwtunnel(struct lwtunnel_state *lwt)
62d1df6fd8SDavid Lebrun {
63d1df6fd8SDavid Lebrun 	return (struct seg6_local_lwt *)lwt->data;
64d1df6fd8SDavid Lebrun }
65d1df6fd8SDavid Lebrun 
66140f04c3SDavid Lebrun static struct ipv6_sr_hdr *get_srh(struct sk_buff *skb)
67140f04c3SDavid Lebrun {
68140f04c3SDavid Lebrun 	struct ipv6_sr_hdr *srh;
695829d70bSAhmed Abdelsalam 	int len, srhoff = 0;
70140f04c3SDavid Lebrun 
715829d70bSAhmed Abdelsalam 	if (ipv6_find_hdr(skb, &srhoff, IPPROTO_ROUTING, NULL, NULL) < 0)
72140f04c3SDavid Lebrun 		return NULL;
73140f04c3SDavid Lebrun 
745829d70bSAhmed Abdelsalam 	if (!pskb_may_pull(skb, srhoff + sizeof(*srh)))
755829d70bSAhmed Abdelsalam 		return NULL;
765829d70bSAhmed Abdelsalam 
775829d70bSAhmed Abdelsalam 	srh = (struct ipv6_sr_hdr *)(skb->data + srhoff);
785829d70bSAhmed Abdelsalam 
79140f04c3SDavid Lebrun 	len = (srh->hdrlen + 1) << 3;
80140f04c3SDavid Lebrun 
815829d70bSAhmed Abdelsalam 	if (!pskb_may_pull(skb, srhoff + len))
82140f04c3SDavid Lebrun 		return NULL;
83140f04c3SDavid Lebrun 
84140f04c3SDavid Lebrun 	if (!seg6_validate_srh(srh, len))
85140f04c3SDavid Lebrun 		return NULL;
86140f04c3SDavid Lebrun 
87140f04c3SDavid Lebrun 	return srh;
88140f04c3SDavid Lebrun }
89140f04c3SDavid Lebrun 
90140f04c3SDavid Lebrun static struct ipv6_sr_hdr *get_and_validate_srh(struct sk_buff *skb)
91140f04c3SDavid Lebrun {
92140f04c3SDavid Lebrun 	struct ipv6_sr_hdr *srh;
93140f04c3SDavid Lebrun 
94140f04c3SDavid Lebrun 	srh = get_srh(skb);
95140f04c3SDavid Lebrun 	if (!srh)
96140f04c3SDavid Lebrun 		return NULL;
97140f04c3SDavid Lebrun 
98140f04c3SDavid Lebrun 	if (srh->segments_left == 0)
99140f04c3SDavid Lebrun 		return NULL;
100140f04c3SDavid Lebrun 
101140f04c3SDavid Lebrun #ifdef CONFIG_IPV6_SEG6_HMAC
102140f04c3SDavid Lebrun 	if (!seg6_hmac_validate_skb(skb))
103140f04c3SDavid Lebrun 		return NULL;
104140f04c3SDavid Lebrun #endif
105140f04c3SDavid Lebrun 
106140f04c3SDavid Lebrun 	return srh;
107140f04c3SDavid Lebrun }
108140f04c3SDavid Lebrun 
109d7a669ddSDavid Lebrun static bool decap_and_validate(struct sk_buff *skb, int proto)
110d7a669ddSDavid Lebrun {
111d7a669ddSDavid Lebrun 	struct ipv6_sr_hdr *srh;
112d7a669ddSDavid Lebrun 	unsigned int off = 0;
113d7a669ddSDavid Lebrun 
114d7a669ddSDavid Lebrun 	srh = get_srh(skb);
115d7a669ddSDavid Lebrun 	if (srh && srh->segments_left > 0)
116d7a669ddSDavid Lebrun 		return false;
117d7a669ddSDavid Lebrun 
118d7a669ddSDavid Lebrun #ifdef CONFIG_IPV6_SEG6_HMAC
119d7a669ddSDavid Lebrun 	if (srh && !seg6_hmac_validate_skb(skb))
120d7a669ddSDavid Lebrun 		return false;
121d7a669ddSDavid Lebrun #endif
122d7a669ddSDavid Lebrun 
123d7a669ddSDavid Lebrun 	if (ipv6_find_hdr(skb, &off, proto, NULL, NULL) < 0)
124d7a669ddSDavid Lebrun 		return false;
125d7a669ddSDavid Lebrun 
126d7a669ddSDavid Lebrun 	if (!pskb_pull(skb, off))
127d7a669ddSDavid Lebrun 		return false;
128d7a669ddSDavid Lebrun 
129d7a669ddSDavid Lebrun 	skb_postpull_rcsum(skb, skb_network_header(skb), off);
130d7a669ddSDavid Lebrun 
131d7a669ddSDavid Lebrun 	skb_reset_network_header(skb);
132d7a669ddSDavid Lebrun 	skb_reset_transport_header(skb);
133d7a669ddSDavid Lebrun 	skb->encapsulation = 0;
134d7a669ddSDavid Lebrun 
135d7a669ddSDavid Lebrun 	return true;
136d7a669ddSDavid Lebrun }
137d7a669ddSDavid Lebrun 
138d7a669ddSDavid Lebrun static void advance_nextseg(struct ipv6_sr_hdr *srh, struct in6_addr *daddr)
139d7a669ddSDavid Lebrun {
140d7a669ddSDavid Lebrun 	struct in6_addr *addr;
141d7a669ddSDavid Lebrun 
142d7a669ddSDavid Lebrun 	srh->segments_left--;
143d7a669ddSDavid Lebrun 	addr = srh->segments + srh->segments_left;
144d7a669ddSDavid Lebrun 	*daddr = *addr;
145d7a669ddSDavid Lebrun }
146d7a669ddSDavid Lebrun 
1471c1e761eSMathieu Xhonneux int seg6_lookup_nexthop(struct sk_buff *skb, struct in6_addr *nhaddr,
148d7a669ddSDavid Lebrun 			u32 tbl_id)
149d7a669ddSDavid Lebrun {
150d7a669ddSDavid Lebrun 	struct net *net = dev_net(skb->dev);
151d7a669ddSDavid Lebrun 	struct ipv6hdr *hdr = ipv6_hdr(skb);
152d7a669ddSDavid Lebrun 	int flags = RT6_LOOKUP_F_HAS_SADDR;
153d7a669ddSDavid Lebrun 	struct dst_entry *dst = NULL;
154d7a669ddSDavid Lebrun 	struct rt6_info *rt;
155d7a669ddSDavid Lebrun 	struct flowi6 fl6;
156d7a669ddSDavid Lebrun 
157d7a669ddSDavid Lebrun 	fl6.flowi6_iif = skb->dev->ifindex;
158d7a669ddSDavid Lebrun 	fl6.daddr = nhaddr ? *nhaddr : hdr->daddr;
159d7a669ddSDavid Lebrun 	fl6.saddr = hdr->saddr;
160d7a669ddSDavid Lebrun 	fl6.flowlabel = ip6_flowinfo(hdr);
161d7a669ddSDavid Lebrun 	fl6.flowi6_mark = skb->mark;
162d7a669ddSDavid Lebrun 	fl6.flowi6_proto = hdr->nexthdr;
163d7a669ddSDavid Lebrun 
164d7a669ddSDavid Lebrun 	if (nhaddr)
165d7a669ddSDavid Lebrun 		fl6.flowi6_flags = FLOWI_FLAG_KNOWN_NH;
166d7a669ddSDavid Lebrun 
167d7a669ddSDavid Lebrun 	if (!tbl_id) {
168b75cc8f9SDavid Ahern 		dst = ip6_route_input_lookup(net, skb->dev, &fl6, skb, flags);
169d7a669ddSDavid Lebrun 	} else {
170d7a669ddSDavid Lebrun 		struct fib6_table *table;
171d7a669ddSDavid Lebrun 
172d7a669ddSDavid Lebrun 		table = fib6_get_table(net, tbl_id);
173d7a669ddSDavid Lebrun 		if (!table)
174d7a669ddSDavid Lebrun 			goto out;
175d7a669ddSDavid Lebrun 
176b75cc8f9SDavid Ahern 		rt = ip6_pol_route(net, table, 0, &fl6, skb, flags);
177d7a669ddSDavid Lebrun 		dst = &rt->dst;
178d7a669ddSDavid Lebrun 	}
179d7a669ddSDavid Lebrun 
180d7a669ddSDavid Lebrun 	if (dst && dst->dev->flags & IFF_LOOPBACK && !dst->error) {
181d7a669ddSDavid Lebrun 		dst_release(dst);
182d7a669ddSDavid Lebrun 		dst = NULL;
183d7a669ddSDavid Lebrun 	}
184d7a669ddSDavid Lebrun 
185d7a669ddSDavid Lebrun out:
186d7a669ddSDavid Lebrun 	if (!dst) {
187d7a669ddSDavid Lebrun 		rt = net->ipv6.ip6_blk_hole_entry;
188d7a669ddSDavid Lebrun 		dst = &rt->dst;
189d7a669ddSDavid Lebrun 		dst_hold(dst);
190d7a669ddSDavid Lebrun 	}
191d7a669ddSDavid Lebrun 
192d7a669ddSDavid Lebrun 	skb_dst_drop(skb);
193d7a669ddSDavid Lebrun 	skb_dst_set(skb, dst);
1941c1e761eSMathieu Xhonneux 	return dst->error;
195d7a669ddSDavid Lebrun }
196d7a669ddSDavid Lebrun 
197140f04c3SDavid Lebrun /* regular endpoint function */
198140f04c3SDavid Lebrun static int input_action_end(struct sk_buff *skb, struct seg6_local_lwt *slwt)
199140f04c3SDavid Lebrun {
200140f04c3SDavid Lebrun 	struct ipv6_sr_hdr *srh;
201140f04c3SDavid Lebrun 
202140f04c3SDavid Lebrun 	srh = get_and_validate_srh(skb);
203140f04c3SDavid Lebrun 	if (!srh)
204140f04c3SDavid Lebrun 		goto drop;
205140f04c3SDavid Lebrun 
206d7a669ddSDavid Lebrun 	advance_nextseg(srh, &ipv6_hdr(skb)->daddr);
207140f04c3SDavid Lebrun 
2081c1e761eSMathieu Xhonneux 	seg6_lookup_nexthop(skb, NULL, 0);
209140f04c3SDavid Lebrun 
210140f04c3SDavid Lebrun 	return dst_input(skb);
211140f04c3SDavid Lebrun 
212140f04c3SDavid Lebrun drop:
213140f04c3SDavid Lebrun 	kfree_skb(skb);
214140f04c3SDavid Lebrun 	return -EINVAL;
215140f04c3SDavid Lebrun }
216140f04c3SDavid Lebrun 
217140f04c3SDavid Lebrun /* regular endpoint, and forward to specified nexthop */
218140f04c3SDavid Lebrun static int input_action_end_x(struct sk_buff *skb, struct seg6_local_lwt *slwt)
219140f04c3SDavid Lebrun {
220140f04c3SDavid Lebrun 	struct ipv6_sr_hdr *srh;
221140f04c3SDavid Lebrun 
222140f04c3SDavid Lebrun 	srh = get_and_validate_srh(skb);
223140f04c3SDavid Lebrun 	if (!srh)
224140f04c3SDavid Lebrun 		goto drop;
225140f04c3SDavid Lebrun 
226d7a669ddSDavid Lebrun 	advance_nextseg(srh, &ipv6_hdr(skb)->daddr);
227140f04c3SDavid Lebrun 
2281c1e761eSMathieu Xhonneux 	seg6_lookup_nexthop(skb, &slwt->nh6, 0);
229140f04c3SDavid Lebrun 
230140f04c3SDavid Lebrun 	return dst_input(skb);
231140f04c3SDavid Lebrun 
232140f04c3SDavid Lebrun drop:
233140f04c3SDavid Lebrun 	kfree_skb(skb);
234140f04c3SDavid Lebrun 	return -EINVAL;
235140f04c3SDavid Lebrun }
236140f04c3SDavid Lebrun 
237891ef8ddSDavid Lebrun static int input_action_end_t(struct sk_buff *skb, struct seg6_local_lwt *slwt)
238891ef8ddSDavid Lebrun {
239891ef8ddSDavid Lebrun 	struct ipv6_sr_hdr *srh;
240891ef8ddSDavid Lebrun 
241891ef8ddSDavid Lebrun 	srh = get_and_validate_srh(skb);
242891ef8ddSDavid Lebrun 	if (!srh)
243891ef8ddSDavid Lebrun 		goto drop;
244891ef8ddSDavid Lebrun 
245891ef8ddSDavid Lebrun 	advance_nextseg(srh, &ipv6_hdr(skb)->daddr);
246891ef8ddSDavid Lebrun 
2471c1e761eSMathieu Xhonneux 	seg6_lookup_nexthop(skb, NULL, slwt->table);
248891ef8ddSDavid Lebrun 
249891ef8ddSDavid Lebrun 	return dst_input(skb);
250891ef8ddSDavid Lebrun 
251891ef8ddSDavid Lebrun drop:
252891ef8ddSDavid Lebrun 	kfree_skb(skb);
253891ef8ddSDavid Lebrun 	return -EINVAL;
254891ef8ddSDavid Lebrun }
255891ef8ddSDavid Lebrun 
256891ef8ddSDavid Lebrun /* decapsulate and forward inner L2 frame on specified interface */
257891ef8ddSDavid Lebrun static int input_action_end_dx2(struct sk_buff *skb,
258891ef8ddSDavid Lebrun 				struct seg6_local_lwt *slwt)
259891ef8ddSDavid Lebrun {
260891ef8ddSDavid Lebrun 	struct net *net = dev_net(skb->dev);
261891ef8ddSDavid Lebrun 	struct net_device *odev;
262891ef8ddSDavid Lebrun 	struct ethhdr *eth;
263891ef8ddSDavid Lebrun 
264891ef8ddSDavid Lebrun 	if (!decap_and_validate(skb, NEXTHDR_NONE))
265891ef8ddSDavid Lebrun 		goto drop;
266891ef8ddSDavid Lebrun 
267891ef8ddSDavid Lebrun 	if (!pskb_may_pull(skb, ETH_HLEN))
268891ef8ddSDavid Lebrun 		goto drop;
269891ef8ddSDavid Lebrun 
270891ef8ddSDavid Lebrun 	skb_reset_mac_header(skb);
271891ef8ddSDavid Lebrun 	eth = (struct ethhdr *)skb->data;
272891ef8ddSDavid Lebrun 
273891ef8ddSDavid Lebrun 	/* To determine the frame's protocol, we assume it is 802.3. This avoids
274891ef8ddSDavid Lebrun 	 * a call to eth_type_trans(), which is not really relevant for our
275891ef8ddSDavid Lebrun 	 * use case.
276891ef8ddSDavid Lebrun 	 */
277891ef8ddSDavid Lebrun 	if (!eth_proto_is_802_3(eth->h_proto))
278891ef8ddSDavid Lebrun 		goto drop;
279891ef8ddSDavid Lebrun 
280891ef8ddSDavid Lebrun 	odev = dev_get_by_index_rcu(net, slwt->oif);
281891ef8ddSDavid Lebrun 	if (!odev)
282891ef8ddSDavid Lebrun 		goto drop;
283891ef8ddSDavid Lebrun 
284891ef8ddSDavid Lebrun 	/* As we accept Ethernet frames, make sure the egress device is of
285891ef8ddSDavid Lebrun 	 * the correct type.
286891ef8ddSDavid Lebrun 	 */
287891ef8ddSDavid Lebrun 	if (odev->type != ARPHRD_ETHER)
288891ef8ddSDavid Lebrun 		goto drop;
289891ef8ddSDavid Lebrun 
290891ef8ddSDavid Lebrun 	if (!(odev->flags & IFF_UP) || !netif_carrier_ok(odev))
291891ef8ddSDavid Lebrun 		goto drop;
292891ef8ddSDavid Lebrun 
293891ef8ddSDavid Lebrun 	skb_orphan(skb);
294891ef8ddSDavid Lebrun 
295891ef8ddSDavid Lebrun 	if (skb_warn_if_lro(skb))
296891ef8ddSDavid Lebrun 		goto drop;
297891ef8ddSDavid Lebrun 
298891ef8ddSDavid Lebrun 	skb_forward_csum(skb);
299891ef8ddSDavid Lebrun 
300891ef8ddSDavid Lebrun 	if (skb->len - ETH_HLEN > odev->mtu)
301891ef8ddSDavid Lebrun 		goto drop;
302891ef8ddSDavid Lebrun 
303891ef8ddSDavid Lebrun 	skb->dev = odev;
304891ef8ddSDavid Lebrun 	skb->protocol = eth->h_proto;
305891ef8ddSDavid Lebrun 
306891ef8ddSDavid Lebrun 	return dev_queue_xmit(skb);
307891ef8ddSDavid Lebrun 
308891ef8ddSDavid Lebrun drop:
309891ef8ddSDavid Lebrun 	kfree_skb(skb);
310891ef8ddSDavid Lebrun 	return -EINVAL;
311891ef8ddSDavid Lebrun }
312891ef8ddSDavid Lebrun 
313140f04c3SDavid Lebrun /* decapsulate and forward to specified nexthop */
314140f04c3SDavid Lebrun static int input_action_end_dx6(struct sk_buff *skb,
315140f04c3SDavid Lebrun 				struct seg6_local_lwt *slwt)
316140f04c3SDavid Lebrun {
317d7a669ddSDavid Lebrun 	struct in6_addr *nhaddr = NULL;
318140f04c3SDavid Lebrun 
319140f04c3SDavid Lebrun 	/* this function accepts IPv6 encapsulated packets, with either
320140f04c3SDavid Lebrun 	 * an SRH with SL=0, or no SRH.
321140f04c3SDavid Lebrun 	 */
322140f04c3SDavid Lebrun 
323d7a669ddSDavid Lebrun 	if (!decap_and_validate(skb, IPPROTO_IPV6))
324140f04c3SDavid Lebrun 		goto drop;
325140f04c3SDavid Lebrun 
326d7a669ddSDavid Lebrun 	if (!pskb_may_pull(skb, sizeof(struct ipv6hdr)))
327140f04c3SDavid Lebrun 		goto drop;
328140f04c3SDavid Lebrun 
329140f04c3SDavid Lebrun 	/* The inner packet is not associated to any local interface,
330140f04c3SDavid Lebrun 	 * so we do not call netif_rx().
331140f04c3SDavid Lebrun 	 *
332140f04c3SDavid Lebrun 	 * If slwt->nh6 is set to ::, then lookup the nexthop for the
333140f04c3SDavid Lebrun 	 * inner packet's DA. Otherwise, use the specified nexthop.
334140f04c3SDavid Lebrun 	 */
335140f04c3SDavid Lebrun 
336d7a669ddSDavid Lebrun 	if (!ipv6_addr_any(&slwt->nh6))
337d7a669ddSDavid Lebrun 		nhaddr = &slwt->nh6;
338140f04c3SDavid Lebrun 
3391c1e761eSMathieu Xhonneux 	seg6_lookup_nexthop(skb, nhaddr, 0);
340140f04c3SDavid Lebrun 
341140f04c3SDavid Lebrun 	return dst_input(skb);
342140f04c3SDavid Lebrun drop:
343140f04c3SDavid Lebrun 	kfree_skb(skb);
344140f04c3SDavid Lebrun 	return -EINVAL;
345140f04c3SDavid Lebrun }
346140f04c3SDavid Lebrun 
347891ef8ddSDavid Lebrun static int input_action_end_dx4(struct sk_buff *skb,
348891ef8ddSDavid Lebrun 				struct seg6_local_lwt *slwt)
349891ef8ddSDavid Lebrun {
350891ef8ddSDavid Lebrun 	struct iphdr *iph;
351891ef8ddSDavid Lebrun 	__be32 nhaddr;
352891ef8ddSDavid Lebrun 	int err;
353891ef8ddSDavid Lebrun 
354891ef8ddSDavid Lebrun 	if (!decap_and_validate(skb, IPPROTO_IPIP))
355891ef8ddSDavid Lebrun 		goto drop;
356891ef8ddSDavid Lebrun 
357891ef8ddSDavid Lebrun 	if (!pskb_may_pull(skb, sizeof(struct iphdr)))
358891ef8ddSDavid Lebrun 		goto drop;
359891ef8ddSDavid Lebrun 
360891ef8ddSDavid Lebrun 	skb->protocol = htons(ETH_P_IP);
361891ef8ddSDavid Lebrun 
362891ef8ddSDavid Lebrun 	iph = ip_hdr(skb);
363891ef8ddSDavid Lebrun 
364891ef8ddSDavid Lebrun 	nhaddr = slwt->nh4.s_addr ?: iph->daddr;
365891ef8ddSDavid Lebrun 
366891ef8ddSDavid Lebrun 	skb_dst_drop(skb);
367891ef8ddSDavid Lebrun 
368891ef8ddSDavid Lebrun 	err = ip_route_input(skb, nhaddr, iph->saddr, 0, skb->dev);
369891ef8ddSDavid Lebrun 	if (err)
370891ef8ddSDavid Lebrun 		goto drop;
371891ef8ddSDavid Lebrun 
372891ef8ddSDavid Lebrun 	return dst_input(skb);
373891ef8ddSDavid Lebrun 
374891ef8ddSDavid Lebrun drop:
375891ef8ddSDavid Lebrun 	kfree_skb(skb);
376891ef8ddSDavid Lebrun 	return -EINVAL;
377891ef8ddSDavid Lebrun }
378891ef8ddSDavid Lebrun 
379891ef8ddSDavid Lebrun static int input_action_end_dt6(struct sk_buff *skb,
380891ef8ddSDavid Lebrun 				struct seg6_local_lwt *slwt)
381891ef8ddSDavid Lebrun {
382891ef8ddSDavid Lebrun 	if (!decap_and_validate(skb, IPPROTO_IPV6))
383891ef8ddSDavid Lebrun 		goto drop;
384891ef8ddSDavid Lebrun 
385891ef8ddSDavid Lebrun 	if (!pskb_may_pull(skb, sizeof(struct ipv6hdr)))
386891ef8ddSDavid Lebrun 		goto drop;
387891ef8ddSDavid Lebrun 
3881c1e761eSMathieu Xhonneux 	seg6_lookup_nexthop(skb, NULL, slwt->table);
389891ef8ddSDavid Lebrun 
390891ef8ddSDavid Lebrun 	return dst_input(skb);
391891ef8ddSDavid Lebrun 
392891ef8ddSDavid Lebrun drop:
393891ef8ddSDavid Lebrun 	kfree_skb(skb);
394891ef8ddSDavid Lebrun 	return -EINVAL;
395891ef8ddSDavid Lebrun }
396891ef8ddSDavid Lebrun 
397140f04c3SDavid Lebrun /* push an SRH on top of the current one */
398140f04c3SDavid Lebrun static int input_action_end_b6(struct sk_buff *skb, struct seg6_local_lwt *slwt)
399140f04c3SDavid Lebrun {
400140f04c3SDavid Lebrun 	struct ipv6_sr_hdr *srh;
401140f04c3SDavid Lebrun 	int err = -EINVAL;
402140f04c3SDavid Lebrun 
403140f04c3SDavid Lebrun 	srh = get_and_validate_srh(skb);
404140f04c3SDavid Lebrun 	if (!srh)
405140f04c3SDavid Lebrun 		goto drop;
406140f04c3SDavid Lebrun 
407140f04c3SDavid Lebrun 	err = seg6_do_srh_inline(skb, slwt->srh);
408140f04c3SDavid Lebrun 	if (err)
409140f04c3SDavid Lebrun 		goto drop;
410140f04c3SDavid Lebrun 
411140f04c3SDavid Lebrun 	ipv6_hdr(skb)->payload_len = htons(skb->len - sizeof(struct ipv6hdr));
412140f04c3SDavid Lebrun 	skb_set_transport_header(skb, sizeof(struct ipv6hdr));
413140f04c3SDavid Lebrun 
4141c1e761eSMathieu Xhonneux 	seg6_lookup_nexthop(skb, NULL, 0);
415140f04c3SDavid Lebrun 
416140f04c3SDavid Lebrun 	return dst_input(skb);
417140f04c3SDavid Lebrun 
418140f04c3SDavid Lebrun drop:
419140f04c3SDavid Lebrun 	kfree_skb(skb);
420140f04c3SDavid Lebrun 	return err;
421140f04c3SDavid Lebrun }
422140f04c3SDavid Lebrun 
423140f04c3SDavid Lebrun /* encapsulate within an outer IPv6 header and a specified SRH */
424140f04c3SDavid Lebrun static int input_action_end_b6_encap(struct sk_buff *skb,
425140f04c3SDavid Lebrun 				     struct seg6_local_lwt *slwt)
426140f04c3SDavid Lebrun {
427140f04c3SDavid Lebrun 	struct ipv6_sr_hdr *srh;
428140f04c3SDavid Lebrun 	int err = -EINVAL;
429140f04c3SDavid Lebrun 
430140f04c3SDavid Lebrun 	srh = get_and_validate_srh(skb);
431140f04c3SDavid Lebrun 	if (!srh)
432140f04c3SDavid Lebrun 		goto drop;
433140f04c3SDavid Lebrun 
434d7a669ddSDavid Lebrun 	advance_nextseg(srh, &ipv6_hdr(skb)->daddr);
435140f04c3SDavid Lebrun 
436140f04c3SDavid Lebrun 	skb_reset_inner_headers(skb);
437140f04c3SDavid Lebrun 	skb->encapsulation = 1;
438140f04c3SDavid Lebrun 
43932d99d0bSDavid Lebrun 	err = seg6_do_srh_encap(skb, slwt->srh, IPPROTO_IPV6);
440140f04c3SDavid Lebrun 	if (err)
441140f04c3SDavid Lebrun 		goto drop;
442140f04c3SDavid Lebrun 
443140f04c3SDavid Lebrun 	ipv6_hdr(skb)->payload_len = htons(skb->len - sizeof(struct ipv6hdr));
444140f04c3SDavid Lebrun 	skb_set_transport_header(skb, sizeof(struct ipv6hdr));
445140f04c3SDavid Lebrun 
4461c1e761eSMathieu Xhonneux 	seg6_lookup_nexthop(skb, NULL, 0);
447140f04c3SDavid Lebrun 
448140f04c3SDavid Lebrun 	return dst_input(skb);
449140f04c3SDavid Lebrun 
450140f04c3SDavid Lebrun drop:
451140f04c3SDavid Lebrun 	kfree_skb(skb);
452140f04c3SDavid Lebrun 	return err;
453140f04c3SDavid Lebrun }
454140f04c3SDavid Lebrun 
455fe94cc29SMathieu Xhonneux DEFINE_PER_CPU(struct seg6_bpf_srh_state, seg6_bpf_srh_states);
456fe94cc29SMathieu Xhonneux 
457486cdf21SMathieu Xhonneux bool seg6_bpf_has_valid_srh(struct sk_buff *skb)
458486cdf21SMathieu Xhonneux {
459486cdf21SMathieu Xhonneux 	struct seg6_bpf_srh_state *srh_state =
460486cdf21SMathieu Xhonneux 		this_cpu_ptr(&seg6_bpf_srh_states);
461486cdf21SMathieu Xhonneux 	struct ipv6_sr_hdr *srh = srh_state->srh;
462486cdf21SMathieu Xhonneux 
463486cdf21SMathieu Xhonneux 	if (unlikely(srh == NULL))
464486cdf21SMathieu Xhonneux 		return false;
465486cdf21SMathieu Xhonneux 
466486cdf21SMathieu Xhonneux 	if (unlikely(!srh_state->valid)) {
467486cdf21SMathieu Xhonneux 		if ((srh_state->hdrlen & 7) != 0)
468486cdf21SMathieu Xhonneux 			return false;
469486cdf21SMathieu Xhonneux 
470486cdf21SMathieu Xhonneux 		srh->hdrlen = (u8)(srh_state->hdrlen >> 3);
471486cdf21SMathieu Xhonneux 		if (!seg6_validate_srh(srh, (srh->hdrlen + 1) << 3))
472486cdf21SMathieu Xhonneux 			return false;
473486cdf21SMathieu Xhonneux 
474486cdf21SMathieu Xhonneux 		srh_state->valid = true;
475486cdf21SMathieu Xhonneux 	}
476486cdf21SMathieu Xhonneux 
477486cdf21SMathieu Xhonneux 	return true;
478486cdf21SMathieu Xhonneux }
479486cdf21SMathieu Xhonneux 
480004d4b27SMathieu Xhonneux static int input_action_end_bpf(struct sk_buff *skb,
481004d4b27SMathieu Xhonneux 				struct seg6_local_lwt *slwt)
482004d4b27SMathieu Xhonneux {
483004d4b27SMathieu Xhonneux 	struct seg6_bpf_srh_state *srh_state =
484004d4b27SMathieu Xhonneux 		this_cpu_ptr(&seg6_bpf_srh_states);
485004d4b27SMathieu Xhonneux 	struct ipv6_sr_hdr *srh;
486004d4b27SMathieu Xhonneux 	int ret;
487004d4b27SMathieu Xhonneux 
488004d4b27SMathieu Xhonneux 	srh = get_and_validate_srh(skb);
489486cdf21SMathieu Xhonneux 	if (!srh) {
490486cdf21SMathieu Xhonneux 		kfree_skb(skb);
491486cdf21SMathieu Xhonneux 		return -EINVAL;
492486cdf21SMathieu Xhonneux 	}
493004d4b27SMathieu Xhonneux 	advance_nextseg(srh, &ipv6_hdr(skb)->daddr);
494004d4b27SMathieu Xhonneux 
495004d4b27SMathieu Xhonneux 	/* preempt_disable is needed to protect the per-CPU buffer srh_state,
496004d4b27SMathieu Xhonneux 	 * which is also accessed by the bpf_lwt_seg6_* helpers
497004d4b27SMathieu Xhonneux 	 */
498004d4b27SMathieu Xhonneux 	preempt_disable();
499486cdf21SMathieu Xhonneux 	srh_state->srh = srh;
500004d4b27SMathieu Xhonneux 	srh_state->hdrlen = srh->hdrlen << 3;
501486cdf21SMathieu Xhonneux 	srh_state->valid = true;
502004d4b27SMathieu Xhonneux 
503004d4b27SMathieu Xhonneux 	rcu_read_lock();
504004d4b27SMathieu Xhonneux 	bpf_compute_data_pointers(skb);
505004d4b27SMathieu Xhonneux 	ret = bpf_prog_run_save_cb(slwt->bpf.prog, skb);
506004d4b27SMathieu Xhonneux 	rcu_read_unlock();
507004d4b27SMathieu Xhonneux 
508004d4b27SMathieu Xhonneux 	switch (ret) {
509004d4b27SMathieu Xhonneux 	case BPF_OK:
510004d4b27SMathieu Xhonneux 	case BPF_REDIRECT:
511004d4b27SMathieu Xhonneux 		break;
512004d4b27SMathieu Xhonneux 	case BPF_DROP:
513004d4b27SMathieu Xhonneux 		goto drop;
514004d4b27SMathieu Xhonneux 	default:
515004d4b27SMathieu Xhonneux 		pr_warn_once("bpf-seg6local: Illegal return value %u\n", ret);
516004d4b27SMathieu Xhonneux 		goto drop;
517004d4b27SMathieu Xhonneux 	}
518004d4b27SMathieu Xhonneux 
519486cdf21SMathieu Xhonneux 	if (srh_state->srh && !seg6_bpf_has_valid_srh(skb))
520004d4b27SMathieu Xhonneux 		goto drop;
521004d4b27SMathieu Xhonneux 
522486cdf21SMathieu Xhonneux 	preempt_enable();
523004d4b27SMathieu Xhonneux 	if (ret != BPF_REDIRECT)
524004d4b27SMathieu Xhonneux 		seg6_lookup_nexthop(skb, NULL, 0);
525004d4b27SMathieu Xhonneux 
526004d4b27SMathieu Xhonneux 	return dst_input(skb);
527004d4b27SMathieu Xhonneux 
528004d4b27SMathieu Xhonneux drop:
529486cdf21SMathieu Xhonneux 	preempt_enable();
530004d4b27SMathieu Xhonneux 	kfree_skb(skb);
531004d4b27SMathieu Xhonneux 	return -EINVAL;
532004d4b27SMathieu Xhonneux }
533004d4b27SMathieu Xhonneux 
534d1df6fd8SDavid Lebrun static struct seg6_action_desc seg6_action_table[] = {
535d1df6fd8SDavid Lebrun 	{
536d1df6fd8SDavid Lebrun 		.action		= SEG6_LOCAL_ACTION_END,
537d1df6fd8SDavid Lebrun 		.attrs		= 0,
538140f04c3SDavid Lebrun 		.input		= input_action_end,
539d1df6fd8SDavid Lebrun 	},
540140f04c3SDavid Lebrun 	{
541140f04c3SDavid Lebrun 		.action		= SEG6_LOCAL_ACTION_END_X,
542140f04c3SDavid Lebrun 		.attrs		= (1 << SEG6_LOCAL_NH6),
543140f04c3SDavid Lebrun 		.input		= input_action_end_x,
544140f04c3SDavid Lebrun 	},
545140f04c3SDavid Lebrun 	{
546891ef8ddSDavid Lebrun 		.action		= SEG6_LOCAL_ACTION_END_T,
547891ef8ddSDavid Lebrun 		.attrs		= (1 << SEG6_LOCAL_TABLE),
548891ef8ddSDavid Lebrun 		.input		= input_action_end_t,
549891ef8ddSDavid Lebrun 	},
550891ef8ddSDavid Lebrun 	{
551891ef8ddSDavid Lebrun 		.action		= SEG6_LOCAL_ACTION_END_DX2,
552891ef8ddSDavid Lebrun 		.attrs		= (1 << SEG6_LOCAL_OIF),
553891ef8ddSDavid Lebrun 		.input		= input_action_end_dx2,
554891ef8ddSDavid Lebrun 	},
555891ef8ddSDavid Lebrun 	{
556140f04c3SDavid Lebrun 		.action		= SEG6_LOCAL_ACTION_END_DX6,
557140f04c3SDavid Lebrun 		.attrs		= (1 << SEG6_LOCAL_NH6),
558140f04c3SDavid Lebrun 		.input		= input_action_end_dx6,
559140f04c3SDavid Lebrun 	},
560140f04c3SDavid Lebrun 	{
561891ef8ddSDavid Lebrun 		.action		= SEG6_LOCAL_ACTION_END_DX4,
562891ef8ddSDavid Lebrun 		.attrs		= (1 << SEG6_LOCAL_NH4),
563891ef8ddSDavid Lebrun 		.input		= input_action_end_dx4,
564891ef8ddSDavid Lebrun 	},
565891ef8ddSDavid Lebrun 	{
566891ef8ddSDavid Lebrun 		.action		= SEG6_LOCAL_ACTION_END_DT6,
567891ef8ddSDavid Lebrun 		.attrs		= (1 << SEG6_LOCAL_TABLE),
568891ef8ddSDavid Lebrun 		.input		= input_action_end_dt6,
569891ef8ddSDavid Lebrun 	},
570891ef8ddSDavid Lebrun 	{
571140f04c3SDavid Lebrun 		.action		= SEG6_LOCAL_ACTION_END_B6,
572140f04c3SDavid Lebrun 		.attrs		= (1 << SEG6_LOCAL_SRH),
573140f04c3SDavid Lebrun 		.input		= input_action_end_b6,
574140f04c3SDavid Lebrun 	},
575140f04c3SDavid Lebrun 	{
576140f04c3SDavid Lebrun 		.action		= SEG6_LOCAL_ACTION_END_B6_ENCAP,
577140f04c3SDavid Lebrun 		.attrs		= (1 << SEG6_LOCAL_SRH),
578140f04c3SDavid Lebrun 		.input		= input_action_end_b6_encap,
579140f04c3SDavid Lebrun 		.static_headroom	= sizeof(struct ipv6hdr),
580004d4b27SMathieu Xhonneux 	},
581004d4b27SMathieu Xhonneux 	{
582004d4b27SMathieu Xhonneux 		.action		= SEG6_LOCAL_ACTION_END_BPF,
583004d4b27SMathieu Xhonneux 		.attrs		= (1 << SEG6_LOCAL_BPF),
584004d4b27SMathieu Xhonneux 		.input		= input_action_end_bpf,
585004d4b27SMathieu Xhonneux 	},
586004d4b27SMathieu Xhonneux 
587d1df6fd8SDavid Lebrun };
588d1df6fd8SDavid Lebrun 
589d1df6fd8SDavid Lebrun static struct seg6_action_desc *__get_action_desc(int action)
590d1df6fd8SDavid Lebrun {
591d1df6fd8SDavid Lebrun 	struct seg6_action_desc *desc;
592d1df6fd8SDavid Lebrun 	int i, count;
593d1df6fd8SDavid Lebrun 
594709af180SColin Ian King 	count = ARRAY_SIZE(seg6_action_table);
595d1df6fd8SDavid Lebrun 	for (i = 0; i < count; i++) {
596d1df6fd8SDavid Lebrun 		desc = &seg6_action_table[i];
597d1df6fd8SDavid Lebrun 		if (desc->action == action)
598d1df6fd8SDavid Lebrun 			return desc;
599d1df6fd8SDavid Lebrun 	}
600d1df6fd8SDavid Lebrun 
601d1df6fd8SDavid Lebrun 	return NULL;
602d1df6fd8SDavid Lebrun }
603d1df6fd8SDavid Lebrun 
604d1df6fd8SDavid Lebrun static int seg6_local_input(struct sk_buff *skb)
605d1df6fd8SDavid Lebrun {
606d1df6fd8SDavid Lebrun 	struct dst_entry *orig_dst = skb_dst(skb);
607d1df6fd8SDavid Lebrun 	struct seg6_action_desc *desc;
608d1df6fd8SDavid Lebrun 	struct seg6_local_lwt *slwt;
609d1df6fd8SDavid Lebrun 
6106285217fSDavid Lebrun 	if (skb->protocol != htons(ETH_P_IPV6)) {
6116285217fSDavid Lebrun 		kfree_skb(skb);
6126285217fSDavid Lebrun 		return -EINVAL;
6136285217fSDavid Lebrun 	}
6146285217fSDavid Lebrun 
615d1df6fd8SDavid Lebrun 	slwt = seg6_local_lwtunnel(orig_dst->lwtstate);
616d1df6fd8SDavid Lebrun 	desc = slwt->desc;
617d1df6fd8SDavid Lebrun 
618d1df6fd8SDavid Lebrun 	return desc->input(skb, slwt);
619d1df6fd8SDavid Lebrun }
620d1df6fd8SDavid Lebrun 
621d1df6fd8SDavid Lebrun static const struct nla_policy seg6_local_policy[SEG6_LOCAL_MAX + 1] = {
622d1df6fd8SDavid Lebrun 	[SEG6_LOCAL_ACTION]	= { .type = NLA_U32 },
623d1df6fd8SDavid Lebrun 	[SEG6_LOCAL_SRH]	= { .type = NLA_BINARY },
624d1df6fd8SDavid Lebrun 	[SEG6_LOCAL_TABLE]	= { .type = NLA_U32 },
625d1df6fd8SDavid Lebrun 	[SEG6_LOCAL_NH4]	= { .type = NLA_BINARY,
626d1df6fd8SDavid Lebrun 				    .len = sizeof(struct in_addr) },
627d1df6fd8SDavid Lebrun 	[SEG6_LOCAL_NH6]	= { .type = NLA_BINARY,
628d1df6fd8SDavid Lebrun 				    .len = sizeof(struct in6_addr) },
629d1df6fd8SDavid Lebrun 	[SEG6_LOCAL_IIF]	= { .type = NLA_U32 },
630d1df6fd8SDavid Lebrun 	[SEG6_LOCAL_OIF]	= { .type = NLA_U32 },
631004d4b27SMathieu Xhonneux 	[SEG6_LOCAL_BPF]	= { .type = NLA_NESTED },
632d1df6fd8SDavid Lebrun };
633d1df6fd8SDavid Lebrun 
6342d9cc60aSDavid Lebrun static int parse_nla_srh(struct nlattr **attrs, struct seg6_local_lwt *slwt)
6352d9cc60aSDavid Lebrun {
6362d9cc60aSDavid Lebrun 	struct ipv6_sr_hdr *srh;
6372d9cc60aSDavid Lebrun 	int len;
6382d9cc60aSDavid Lebrun 
6392d9cc60aSDavid Lebrun 	srh = nla_data(attrs[SEG6_LOCAL_SRH]);
6402d9cc60aSDavid Lebrun 	len = nla_len(attrs[SEG6_LOCAL_SRH]);
6412d9cc60aSDavid Lebrun 
6422d9cc60aSDavid Lebrun 	/* SRH must contain at least one segment */
6432d9cc60aSDavid Lebrun 	if (len < sizeof(*srh) + sizeof(struct in6_addr))
6442d9cc60aSDavid Lebrun 		return -EINVAL;
6452d9cc60aSDavid Lebrun 
6462d9cc60aSDavid Lebrun 	if (!seg6_validate_srh(srh, len))
6472d9cc60aSDavid Lebrun 		return -EINVAL;
6482d9cc60aSDavid Lebrun 
6497fa41efaSYueHaibing 	slwt->srh = kmemdup(srh, len, GFP_KERNEL);
6502d9cc60aSDavid Lebrun 	if (!slwt->srh)
6512d9cc60aSDavid Lebrun 		return -ENOMEM;
6522d9cc60aSDavid Lebrun 
6532d9cc60aSDavid Lebrun 	slwt->headroom += len;
6542d9cc60aSDavid Lebrun 
6552d9cc60aSDavid Lebrun 	return 0;
6562d9cc60aSDavid Lebrun }
6572d9cc60aSDavid Lebrun 
6582d9cc60aSDavid Lebrun static int put_nla_srh(struct sk_buff *skb, struct seg6_local_lwt *slwt)
6592d9cc60aSDavid Lebrun {
6602d9cc60aSDavid Lebrun 	struct ipv6_sr_hdr *srh;
6612d9cc60aSDavid Lebrun 	struct nlattr *nla;
6622d9cc60aSDavid Lebrun 	int len;
6632d9cc60aSDavid Lebrun 
6642d9cc60aSDavid Lebrun 	srh = slwt->srh;
6652d9cc60aSDavid Lebrun 	len = (srh->hdrlen + 1) << 3;
6662d9cc60aSDavid Lebrun 
6672d9cc60aSDavid Lebrun 	nla = nla_reserve(skb, SEG6_LOCAL_SRH, len);
6682d9cc60aSDavid Lebrun 	if (!nla)
6692d9cc60aSDavid Lebrun 		return -EMSGSIZE;
6702d9cc60aSDavid Lebrun 
6712d9cc60aSDavid Lebrun 	memcpy(nla_data(nla), srh, len);
6722d9cc60aSDavid Lebrun 
6732d9cc60aSDavid Lebrun 	return 0;
6742d9cc60aSDavid Lebrun }
6752d9cc60aSDavid Lebrun 
6762d9cc60aSDavid Lebrun static int cmp_nla_srh(struct seg6_local_lwt *a, struct seg6_local_lwt *b)
6772d9cc60aSDavid Lebrun {
6782d9cc60aSDavid Lebrun 	int len = (a->srh->hdrlen + 1) << 3;
6792d9cc60aSDavid Lebrun 
6802d9cc60aSDavid Lebrun 	if (len != ((b->srh->hdrlen + 1) << 3))
6812d9cc60aSDavid Lebrun 		return 1;
6822d9cc60aSDavid Lebrun 
6832d9cc60aSDavid Lebrun 	return memcmp(a->srh, b->srh, len);
6842d9cc60aSDavid Lebrun }
6852d9cc60aSDavid Lebrun 
6862d9cc60aSDavid Lebrun static int parse_nla_table(struct nlattr **attrs, struct seg6_local_lwt *slwt)
6872d9cc60aSDavid Lebrun {
6882d9cc60aSDavid Lebrun 	slwt->table = nla_get_u32(attrs[SEG6_LOCAL_TABLE]);
6892d9cc60aSDavid Lebrun 
6902d9cc60aSDavid Lebrun 	return 0;
6912d9cc60aSDavid Lebrun }
6922d9cc60aSDavid Lebrun 
6932d9cc60aSDavid Lebrun static int put_nla_table(struct sk_buff *skb, struct seg6_local_lwt *slwt)
6942d9cc60aSDavid Lebrun {
6952d9cc60aSDavid Lebrun 	if (nla_put_u32(skb, SEG6_LOCAL_TABLE, slwt->table))
6962d9cc60aSDavid Lebrun 		return -EMSGSIZE;
6972d9cc60aSDavid Lebrun 
6982d9cc60aSDavid Lebrun 	return 0;
6992d9cc60aSDavid Lebrun }
7002d9cc60aSDavid Lebrun 
7012d9cc60aSDavid Lebrun static int cmp_nla_table(struct seg6_local_lwt *a, struct seg6_local_lwt *b)
7022d9cc60aSDavid Lebrun {
7032d9cc60aSDavid Lebrun 	if (a->table != b->table)
7042d9cc60aSDavid Lebrun 		return 1;
7052d9cc60aSDavid Lebrun 
7062d9cc60aSDavid Lebrun 	return 0;
7072d9cc60aSDavid Lebrun }
7082d9cc60aSDavid Lebrun 
7092d9cc60aSDavid Lebrun static int parse_nla_nh4(struct nlattr **attrs, struct seg6_local_lwt *slwt)
7102d9cc60aSDavid Lebrun {
7112d9cc60aSDavid Lebrun 	memcpy(&slwt->nh4, nla_data(attrs[SEG6_LOCAL_NH4]),
7122d9cc60aSDavid Lebrun 	       sizeof(struct in_addr));
7132d9cc60aSDavid Lebrun 
7142d9cc60aSDavid Lebrun 	return 0;
7152d9cc60aSDavid Lebrun }
7162d9cc60aSDavid Lebrun 
7172d9cc60aSDavid Lebrun static int put_nla_nh4(struct sk_buff *skb, struct seg6_local_lwt *slwt)
7182d9cc60aSDavid Lebrun {
7192d9cc60aSDavid Lebrun 	struct nlattr *nla;
7202d9cc60aSDavid Lebrun 
7212d9cc60aSDavid Lebrun 	nla = nla_reserve(skb, SEG6_LOCAL_NH4, sizeof(struct in_addr));
7222d9cc60aSDavid Lebrun 	if (!nla)
7232d9cc60aSDavid Lebrun 		return -EMSGSIZE;
7242d9cc60aSDavid Lebrun 
7252d9cc60aSDavid Lebrun 	memcpy(nla_data(nla), &slwt->nh4, sizeof(struct in_addr));
7262d9cc60aSDavid Lebrun 
7272d9cc60aSDavid Lebrun 	return 0;
7282d9cc60aSDavid Lebrun }
7292d9cc60aSDavid Lebrun 
7302d9cc60aSDavid Lebrun static int cmp_nla_nh4(struct seg6_local_lwt *a, struct seg6_local_lwt *b)
7312d9cc60aSDavid Lebrun {
7322d9cc60aSDavid Lebrun 	return memcmp(&a->nh4, &b->nh4, sizeof(struct in_addr));
7332d9cc60aSDavid Lebrun }
7342d9cc60aSDavid Lebrun 
7352d9cc60aSDavid Lebrun static int parse_nla_nh6(struct nlattr **attrs, struct seg6_local_lwt *slwt)
7362d9cc60aSDavid Lebrun {
7372d9cc60aSDavid Lebrun 	memcpy(&slwt->nh6, nla_data(attrs[SEG6_LOCAL_NH6]),
7382d9cc60aSDavid Lebrun 	       sizeof(struct in6_addr));
7392d9cc60aSDavid Lebrun 
7402d9cc60aSDavid Lebrun 	return 0;
7412d9cc60aSDavid Lebrun }
7422d9cc60aSDavid Lebrun 
7432d9cc60aSDavid Lebrun static int put_nla_nh6(struct sk_buff *skb, struct seg6_local_lwt *slwt)
7442d9cc60aSDavid Lebrun {
7452d9cc60aSDavid Lebrun 	struct nlattr *nla;
7462d9cc60aSDavid Lebrun 
7472d9cc60aSDavid Lebrun 	nla = nla_reserve(skb, SEG6_LOCAL_NH6, sizeof(struct in6_addr));
7482d9cc60aSDavid Lebrun 	if (!nla)
7492d9cc60aSDavid Lebrun 		return -EMSGSIZE;
7502d9cc60aSDavid Lebrun 
7512d9cc60aSDavid Lebrun 	memcpy(nla_data(nla), &slwt->nh6, sizeof(struct in6_addr));
7522d9cc60aSDavid Lebrun 
7532d9cc60aSDavid Lebrun 	return 0;
7542d9cc60aSDavid Lebrun }
7552d9cc60aSDavid Lebrun 
7562d9cc60aSDavid Lebrun static int cmp_nla_nh6(struct seg6_local_lwt *a, struct seg6_local_lwt *b)
7572d9cc60aSDavid Lebrun {
7582d9cc60aSDavid Lebrun 	return memcmp(&a->nh6, &b->nh6, sizeof(struct in6_addr));
7592d9cc60aSDavid Lebrun }
7602d9cc60aSDavid Lebrun 
7612d9cc60aSDavid Lebrun static int parse_nla_iif(struct nlattr **attrs, struct seg6_local_lwt *slwt)
7622d9cc60aSDavid Lebrun {
7632d9cc60aSDavid Lebrun 	slwt->iif = nla_get_u32(attrs[SEG6_LOCAL_IIF]);
7642d9cc60aSDavid Lebrun 
7652d9cc60aSDavid Lebrun 	return 0;
7662d9cc60aSDavid Lebrun }
7672d9cc60aSDavid Lebrun 
7682d9cc60aSDavid Lebrun static int put_nla_iif(struct sk_buff *skb, struct seg6_local_lwt *slwt)
7692d9cc60aSDavid Lebrun {
7702d9cc60aSDavid Lebrun 	if (nla_put_u32(skb, SEG6_LOCAL_IIF, slwt->iif))
7712d9cc60aSDavid Lebrun 		return -EMSGSIZE;
7722d9cc60aSDavid Lebrun 
7732d9cc60aSDavid Lebrun 	return 0;
7742d9cc60aSDavid Lebrun }
7752d9cc60aSDavid Lebrun 
7762d9cc60aSDavid Lebrun static int cmp_nla_iif(struct seg6_local_lwt *a, struct seg6_local_lwt *b)
7772d9cc60aSDavid Lebrun {
7782d9cc60aSDavid Lebrun 	if (a->iif != b->iif)
7792d9cc60aSDavid Lebrun 		return 1;
7802d9cc60aSDavid Lebrun 
7812d9cc60aSDavid Lebrun 	return 0;
7822d9cc60aSDavid Lebrun }
7832d9cc60aSDavid Lebrun 
7842d9cc60aSDavid Lebrun static int parse_nla_oif(struct nlattr **attrs, struct seg6_local_lwt *slwt)
7852d9cc60aSDavid Lebrun {
7862d9cc60aSDavid Lebrun 	slwt->oif = nla_get_u32(attrs[SEG6_LOCAL_OIF]);
7872d9cc60aSDavid Lebrun 
7882d9cc60aSDavid Lebrun 	return 0;
7892d9cc60aSDavid Lebrun }
7902d9cc60aSDavid Lebrun 
7912d9cc60aSDavid Lebrun static int put_nla_oif(struct sk_buff *skb, struct seg6_local_lwt *slwt)
7922d9cc60aSDavid Lebrun {
7932d9cc60aSDavid Lebrun 	if (nla_put_u32(skb, SEG6_LOCAL_OIF, slwt->oif))
7942d9cc60aSDavid Lebrun 		return -EMSGSIZE;
7952d9cc60aSDavid Lebrun 
7962d9cc60aSDavid Lebrun 	return 0;
7972d9cc60aSDavid Lebrun }
7982d9cc60aSDavid Lebrun 
7992d9cc60aSDavid Lebrun static int cmp_nla_oif(struct seg6_local_lwt *a, struct seg6_local_lwt *b)
8002d9cc60aSDavid Lebrun {
8012d9cc60aSDavid Lebrun 	if (a->oif != b->oif)
8022d9cc60aSDavid Lebrun 		return 1;
8032d9cc60aSDavid Lebrun 
8042d9cc60aSDavid Lebrun 	return 0;
8052d9cc60aSDavid Lebrun }
8062d9cc60aSDavid Lebrun 
807004d4b27SMathieu Xhonneux #define MAX_PROG_NAME 256
808004d4b27SMathieu Xhonneux static const struct nla_policy bpf_prog_policy[SEG6_LOCAL_BPF_PROG_MAX + 1] = {
809004d4b27SMathieu Xhonneux 	[SEG6_LOCAL_BPF_PROG]	   = { .type = NLA_U32, },
810004d4b27SMathieu Xhonneux 	[SEG6_LOCAL_BPF_PROG_NAME] = { .type = NLA_NUL_STRING,
811004d4b27SMathieu Xhonneux 				       .len = MAX_PROG_NAME },
812004d4b27SMathieu Xhonneux };
813004d4b27SMathieu Xhonneux 
814004d4b27SMathieu Xhonneux static int parse_nla_bpf(struct nlattr **attrs, struct seg6_local_lwt *slwt)
815004d4b27SMathieu Xhonneux {
816004d4b27SMathieu Xhonneux 	struct nlattr *tb[SEG6_LOCAL_BPF_PROG_MAX + 1];
817004d4b27SMathieu Xhonneux 	struct bpf_prog *p;
818004d4b27SMathieu Xhonneux 	int ret;
819004d4b27SMathieu Xhonneux 	u32 fd;
820004d4b27SMathieu Xhonneux 
8218cb08174SJohannes Berg 	ret = nla_parse_nested_deprecated(tb, SEG6_LOCAL_BPF_PROG_MAX,
8228cb08174SJohannes Berg 					  attrs[SEG6_LOCAL_BPF],
8238cb08174SJohannes Berg 					  bpf_prog_policy, NULL);
824004d4b27SMathieu Xhonneux 	if (ret < 0)
825004d4b27SMathieu Xhonneux 		return ret;
826004d4b27SMathieu Xhonneux 
827004d4b27SMathieu Xhonneux 	if (!tb[SEG6_LOCAL_BPF_PROG] || !tb[SEG6_LOCAL_BPF_PROG_NAME])
828004d4b27SMathieu Xhonneux 		return -EINVAL;
829004d4b27SMathieu Xhonneux 
830004d4b27SMathieu Xhonneux 	slwt->bpf.name = nla_memdup(tb[SEG6_LOCAL_BPF_PROG_NAME], GFP_KERNEL);
831004d4b27SMathieu Xhonneux 	if (!slwt->bpf.name)
832004d4b27SMathieu Xhonneux 		return -ENOMEM;
833004d4b27SMathieu Xhonneux 
834004d4b27SMathieu Xhonneux 	fd = nla_get_u32(tb[SEG6_LOCAL_BPF_PROG]);
835004d4b27SMathieu Xhonneux 	p = bpf_prog_get_type(fd, BPF_PROG_TYPE_LWT_SEG6LOCAL);
836004d4b27SMathieu Xhonneux 	if (IS_ERR(p)) {
837004d4b27SMathieu Xhonneux 		kfree(slwt->bpf.name);
838004d4b27SMathieu Xhonneux 		return PTR_ERR(p);
839004d4b27SMathieu Xhonneux 	}
840004d4b27SMathieu Xhonneux 
841004d4b27SMathieu Xhonneux 	slwt->bpf.prog = p;
842004d4b27SMathieu Xhonneux 	return 0;
843004d4b27SMathieu Xhonneux }
844004d4b27SMathieu Xhonneux 
845004d4b27SMathieu Xhonneux static int put_nla_bpf(struct sk_buff *skb, struct seg6_local_lwt *slwt)
846004d4b27SMathieu Xhonneux {
847004d4b27SMathieu Xhonneux 	struct nlattr *nest;
848004d4b27SMathieu Xhonneux 
849004d4b27SMathieu Xhonneux 	if (!slwt->bpf.prog)
850004d4b27SMathieu Xhonneux 		return 0;
851004d4b27SMathieu Xhonneux 
852ae0be8deSMichal Kubecek 	nest = nla_nest_start_noflag(skb, SEG6_LOCAL_BPF);
853004d4b27SMathieu Xhonneux 	if (!nest)
854004d4b27SMathieu Xhonneux 		return -EMSGSIZE;
855004d4b27SMathieu Xhonneux 
856004d4b27SMathieu Xhonneux 	if (nla_put_u32(skb, SEG6_LOCAL_BPF_PROG, slwt->bpf.prog->aux->id))
857004d4b27SMathieu Xhonneux 		return -EMSGSIZE;
858004d4b27SMathieu Xhonneux 
859004d4b27SMathieu Xhonneux 	if (slwt->bpf.name &&
860004d4b27SMathieu Xhonneux 	    nla_put_string(skb, SEG6_LOCAL_BPF_PROG_NAME, slwt->bpf.name))
861004d4b27SMathieu Xhonneux 		return -EMSGSIZE;
862004d4b27SMathieu Xhonneux 
863004d4b27SMathieu Xhonneux 	return nla_nest_end(skb, nest);
864004d4b27SMathieu Xhonneux }
865004d4b27SMathieu Xhonneux 
866004d4b27SMathieu Xhonneux static int cmp_nla_bpf(struct seg6_local_lwt *a, struct seg6_local_lwt *b)
867004d4b27SMathieu Xhonneux {
868004d4b27SMathieu Xhonneux 	if (!a->bpf.name && !b->bpf.name)
869004d4b27SMathieu Xhonneux 		return 0;
870004d4b27SMathieu Xhonneux 
871004d4b27SMathieu Xhonneux 	if (!a->bpf.name || !b->bpf.name)
872004d4b27SMathieu Xhonneux 		return 1;
873004d4b27SMathieu Xhonneux 
874004d4b27SMathieu Xhonneux 	return strcmp(a->bpf.name, b->bpf.name);
875004d4b27SMathieu Xhonneux }
876004d4b27SMathieu Xhonneux 
877d1df6fd8SDavid Lebrun struct seg6_action_param {
878d1df6fd8SDavid Lebrun 	int (*parse)(struct nlattr **attrs, struct seg6_local_lwt *slwt);
879d1df6fd8SDavid Lebrun 	int (*put)(struct sk_buff *skb, struct seg6_local_lwt *slwt);
880d1df6fd8SDavid Lebrun 	int (*cmp)(struct seg6_local_lwt *a, struct seg6_local_lwt *b);
881d1df6fd8SDavid Lebrun };
882d1df6fd8SDavid Lebrun 
883d1df6fd8SDavid Lebrun static struct seg6_action_param seg6_action_params[SEG6_LOCAL_MAX + 1] = {
8842d9cc60aSDavid Lebrun 	[SEG6_LOCAL_SRH]	= { .parse = parse_nla_srh,
8852d9cc60aSDavid Lebrun 				    .put = put_nla_srh,
8862d9cc60aSDavid Lebrun 				    .cmp = cmp_nla_srh },
887d1df6fd8SDavid Lebrun 
8882d9cc60aSDavid Lebrun 	[SEG6_LOCAL_TABLE]	= { .parse = parse_nla_table,
8892d9cc60aSDavid Lebrun 				    .put = put_nla_table,
8902d9cc60aSDavid Lebrun 				    .cmp = cmp_nla_table },
891d1df6fd8SDavid Lebrun 
8922d9cc60aSDavid Lebrun 	[SEG6_LOCAL_NH4]	= { .parse = parse_nla_nh4,
8932d9cc60aSDavid Lebrun 				    .put = put_nla_nh4,
8942d9cc60aSDavid Lebrun 				    .cmp = cmp_nla_nh4 },
895d1df6fd8SDavid Lebrun 
8962d9cc60aSDavid Lebrun 	[SEG6_LOCAL_NH6]	= { .parse = parse_nla_nh6,
8972d9cc60aSDavid Lebrun 				    .put = put_nla_nh6,
8982d9cc60aSDavid Lebrun 				    .cmp = cmp_nla_nh6 },
899d1df6fd8SDavid Lebrun 
9002d9cc60aSDavid Lebrun 	[SEG6_LOCAL_IIF]	= { .parse = parse_nla_iif,
9012d9cc60aSDavid Lebrun 				    .put = put_nla_iif,
9022d9cc60aSDavid Lebrun 				    .cmp = cmp_nla_iif },
903d1df6fd8SDavid Lebrun 
9042d9cc60aSDavid Lebrun 	[SEG6_LOCAL_OIF]	= { .parse = parse_nla_oif,
9052d9cc60aSDavid Lebrun 				    .put = put_nla_oif,
9062d9cc60aSDavid Lebrun 				    .cmp = cmp_nla_oif },
907004d4b27SMathieu Xhonneux 
908004d4b27SMathieu Xhonneux 	[SEG6_LOCAL_BPF]	= { .parse = parse_nla_bpf,
909004d4b27SMathieu Xhonneux 				    .put = put_nla_bpf,
910004d4b27SMathieu Xhonneux 				    .cmp = cmp_nla_bpf },
911004d4b27SMathieu Xhonneux 
912d1df6fd8SDavid Lebrun };
913d1df6fd8SDavid Lebrun 
914d1df6fd8SDavid Lebrun static int parse_nla_action(struct nlattr **attrs, struct seg6_local_lwt *slwt)
915d1df6fd8SDavid Lebrun {
916d1df6fd8SDavid Lebrun 	struct seg6_action_param *param;
917d1df6fd8SDavid Lebrun 	struct seg6_action_desc *desc;
918d1df6fd8SDavid Lebrun 	int i, err;
919d1df6fd8SDavid Lebrun 
920d1df6fd8SDavid Lebrun 	desc = __get_action_desc(slwt->action);
921d1df6fd8SDavid Lebrun 	if (!desc)
922d1df6fd8SDavid Lebrun 		return -EINVAL;
923d1df6fd8SDavid Lebrun 
924d1df6fd8SDavid Lebrun 	if (!desc->input)
925d1df6fd8SDavid Lebrun 		return -EOPNOTSUPP;
926d1df6fd8SDavid Lebrun 
927d1df6fd8SDavid Lebrun 	slwt->desc = desc;
928d1df6fd8SDavid Lebrun 	slwt->headroom += desc->static_headroom;
929d1df6fd8SDavid Lebrun 
930d1df6fd8SDavid Lebrun 	for (i = 0; i < SEG6_LOCAL_MAX + 1; i++) {
931d1df6fd8SDavid Lebrun 		if (desc->attrs & (1 << i)) {
932d1df6fd8SDavid Lebrun 			if (!attrs[i])
933d1df6fd8SDavid Lebrun 				return -EINVAL;
934d1df6fd8SDavid Lebrun 
935d1df6fd8SDavid Lebrun 			param = &seg6_action_params[i];
936d1df6fd8SDavid Lebrun 
937d1df6fd8SDavid Lebrun 			err = param->parse(attrs, slwt);
938d1df6fd8SDavid Lebrun 			if (err < 0)
939d1df6fd8SDavid Lebrun 				return err;
940d1df6fd8SDavid Lebrun 		}
941d1df6fd8SDavid Lebrun 	}
942d1df6fd8SDavid Lebrun 
943d1df6fd8SDavid Lebrun 	return 0;
944d1df6fd8SDavid Lebrun }
945d1df6fd8SDavid Lebrun 
946d1df6fd8SDavid Lebrun static int seg6_local_build_state(struct nlattr *nla, unsigned int family,
947d1df6fd8SDavid Lebrun 				  const void *cfg, struct lwtunnel_state **ts,
948d1df6fd8SDavid Lebrun 				  struct netlink_ext_ack *extack)
949d1df6fd8SDavid Lebrun {
950d1df6fd8SDavid Lebrun 	struct nlattr *tb[SEG6_LOCAL_MAX + 1];
951d1df6fd8SDavid Lebrun 	struct lwtunnel_state *newts;
952d1df6fd8SDavid Lebrun 	struct seg6_local_lwt *slwt;
953d1df6fd8SDavid Lebrun 	int err;
954d1df6fd8SDavid Lebrun 
9556285217fSDavid Lebrun 	if (family != AF_INET6)
9566285217fSDavid Lebrun 		return -EINVAL;
9576285217fSDavid Lebrun 
9588cb08174SJohannes Berg 	err = nla_parse_nested_deprecated(tb, SEG6_LOCAL_MAX, nla,
9598cb08174SJohannes Berg 					  seg6_local_policy, extack);
960d1df6fd8SDavid Lebrun 
961d1df6fd8SDavid Lebrun 	if (err < 0)
962d1df6fd8SDavid Lebrun 		return err;
963d1df6fd8SDavid Lebrun 
964d1df6fd8SDavid Lebrun 	if (!tb[SEG6_LOCAL_ACTION])
965d1df6fd8SDavid Lebrun 		return -EINVAL;
966d1df6fd8SDavid Lebrun 
967d1df6fd8SDavid Lebrun 	newts = lwtunnel_state_alloc(sizeof(*slwt));
968d1df6fd8SDavid Lebrun 	if (!newts)
969d1df6fd8SDavid Lebrun 		return -ENOMEM;
970d1df6fd8SDavid Lebrun 
971d1df6fd8SDavid Lebrun 	slwt = seg6_local_lwtunnel(newts);
972d1df6fd8SDavid Lebrun 	slwt->action = nla_get_u32(tb[SEG6_LOCAL_ACTION]);
973d1df6fd8SDavid Lebrun 
974d1df6fd8SDavid Lebrun 	err = parse_nla_action(tb, slwt);
975d1df6fd8SDavid Lebrun 	if (err < 0)
976d1df6fd8SDavid Lebrun 		goto out_free;
977d1df6fd8SDavid Lebrun 
978d1df6fd8SDavid Lebrun 	newts->type = LWTUNNEL_ENCAP_SEG6_LOCAL;
979d1df6fd8SDavid Lebrun 	newts->flags = LWTUNNEL_STATE_INPUT_REDIRECT;
980d1df6fd8SDavid Lebrun 	newts->headroom = slwt->headroom;
981d1df6fd8SDavid Lebrun 
982d1df6fd8SDavid Lebrun 	*ts = newts;
983d1df6fd8SDavid Lebrun 
984d1df6fd8SDavid Lebrun 	return 0;
985d1df6fd8SDavid Lebrun 
986d1df6fd8SDavid Lebrun out_free:
987d1df6fd8SDavid Lebrun 	kfree(slwt->srh);
988d1df6fd8SDavid Lebrun 	kfree(newts);
989d1df6fd8SDavid Lebrun 	return err;
990d1df6fd8SDavid Lebrun }
991d1df6fd8SDavid Lebrun 
992d1df6fd8SDavid Lebrun static void seg6_local_destroy_state(struct lwtunnel_state *lwt)
993d1df6fd8SDavid Lebrun {
994d1df6fd8SDavid Lebrun 	struct seg6_local_lwt *slwt = seg6_local_lwtunnel(lwt);
995d1df6fd8SDavid Lebrun 
996d1df6fd8SDavid Lebrun 	kfree(slwt->srh);
997004d4b27SMathieu Xhonneux 
998004d4b27SMathieu Xhonneux 	if (slwt->desc->attrs & (1 << SEG6_LOCAL_BPF)) {
999004d4b27SMathieu Xhonneux 		kfree(slwt->bpf.name);
1000004d4b27SMathieu Xhonneux 		bpf_prog_put(slwt->bpf.prog);
1001004d4b27SMathieu Xhonneux 	}
1002004d4b27SMathieu Xhonneux 
1003004d4b27SMathieu Xhonneux 	return;
1004d1df6fd8SDavid Lebrun }
1005d1df6fd8SDavid Lebrun 
1006d1df6fd8SDavid Lebrun static int seg6_local_fill_encap(struct sk_buff *skb,
1007d1df6fd8SDavid Lebrun 				 struct lwtunnel_state *lwt)
1008d1df6fd8SDavid Lebrun {
1009d1df6fd8SDavid Lebrun 	struct seg6_local_lwt *slwt = seg6_local_lwtunnel(lwt);
1010d1df6fd8SDavid Lebrun 	struct seg6_action_param *param;
1011d1df6fd8SDavid Lebrun 	int i, err;
1012d1df6fd8SDavid Lebrun 
1013d1df6fd8SDavid Lebrun 	if (nla_put_u32(skb, SEG6_LOCAL_ACTION, slwt->action))
1014d1df6fd8SDavid Lebrun 		return -EMSGSIZE;
1015d1df6fd8SDavid Lebrun 
1016d1df6fd8SDavid Lebrun 	for (i = 0; i < SEG6_LOCAL_MAX + 1; i++) {
1017d1df6fd8SDavid Lebrun 		if (slwt->desc->attrs & (1 << i)) {
1018d1df6fd8SDavid Lebrun 			param = &seg6_action_params[i];
1019d1df6fd8SDavid Lebrun 			err = param->put(skb, slwt);
1020d1df6fd8SDavid Lebrun 			if (err < 0)
1021d1df6fd8SDavid Lebrun 				return err;
1022d1df6fd8SDavid Lebrun 		}
1023d1df6fd8SDavid Lebrun 	}
1024d1df6fd8SDavid Lebrun 
1025d1df6fd8SDavid Lebrun 	return 0;
1026d1df6fd8SDavid Lebrun }
1027d1df6fd8SDavid Lebrun 
1028d1df6fd8SDavid Lebrun static int seg6_local_get_encap_size(struct lwtunnel_state *lwt)
1029d1df6fd8SDavid Lebrun {
1030d1df6fd8SDavid Lebrun 	struct seg6_local_lwt *slwt = seg6_local_lwtunnel(lwt);
1031d1df6fd8SDavid Lebrun 	unsigned long attrs;
1032d1df6fd8SDavid Lebrun 	int nlsize;
1033d1df6fd8SDavid Lebrun 
1034d1df6fd8SDavid Lebrun 	nlsize = nla_total_size(4); /* action */
1035d1df6fd8SDavid Lebrun 
1036d1df6fd8SDavid Lebrun 	attrs = slwt->desc->attrs;
1037d1df6fd8SDavid Lebrun 
1038d1df6fd8SDavid Lebrun 	if (attrs & (1 << SEG6_LOCAL_SRH))
1039d1df6fd8SDavid Lebrun 		nlsize += nla_total_size((slwt->srh->hdrlen + 1) << 3);
1040d1df6fd8SDavid Lebrun 
1041d1df6fd8SDavid Lebrun 	if (attrs & (1 << SEG6_LOCAL_TABLE))
1042d1df6fd8SDavid Lebrun 		nlsize += nla_total_size(4);
1043d1df6fd8SDavid Lebrun 
1044d1df6fd8SDavid Lebrun 	if (attrs & (1 << SEG6_LOCAL_NH4))
1045d1df6fd8SDavid Lebrun 		nlsize += nla_total_size(4);
1046d1df6fd8SDavid Lebrun 
1047d1df6fd8SDavid Lebrun 	if (attrs & (1 << SEG6_LOCAL_NH6))
1048d1df6fd8SDavid Lebrun 		nlsize += nla_total_size(16);
1049d1df6fd8SDavid Lebrun 
1050d1df6fd8SDavid Lebrun 	if (attrs & (1 << SEG6_LOCAL_IIF))
1051d1df6fd8SDavid Lebrun 		nlsize += nla_total_size(4);
1052d1df6fd8SDavid Lebrun 
1053d1df6fd8SDavid Lebrun 	if (attrs & (1 << SEG6_LOCAL_OIF))
1054d1df6fd8SDavid Lebrun 		nlsize += nla_total_size(4);
1055d1df6fd8SDavid Lebrun 
1056004d4b27SMathieu Xhonneux 	if (attrs & (1 << SEG6_LOCAL_BPF))
1057004d4b27SMathieu Xhonneux 		nlsize += nla_total_size(sizeof(struct nlattr)) +
1058004d4b27SMathieu Xhonneux 		       nla_total_size(MAX_PROG_NAME) +
1059004d4b27SMathieu Xhonneux 		       nla_total_size(4);
1060004d4b27SMathieu Xhonneux 
1061d1df6fd8SDavid Lebrun 	return nlsize;
1062d1df6fd8SDavid Lebrun }
1063d1df6fd8SDavid Lebrun 
1064d1df6fd8SDavid Lebrun static int seg6_local_cmp_encap(struct lwtunnel_state *a,
1065d1df6fd8SDavid Lebrun 				struct lwtunnel_state *b)
1066d1df6fd8SDavid Lebrun {
1067d1df6fd8SDavid Lebrun 	struct seg6_local_lwt *slwt_a, *slwt_b;
1068d1df6fd8SDavid Lebrun 	struct seg6_action_param *param;
1069d1df6fd8SDavid Lebrun 	int i;
1070d1df6fd8SDavid Lebrun 
1071d1df6fd8SDavid Lebrun 	slwt_a = seg6_local_lwtunnel(a);
1072d1df6fd8SDavid Lebrun 	slwt_b = seg6_local_lwtunnel(b);
1073d1df6fd8SDavid Lebrun 
1074d1df6fd8SDavid Lebrun 	if (slwt_a->action != slwt_b->action)
1075d1df6fd8SDavid Lebrun 		return 1;
1076d1df6fd8SDavid Lebrun 
1077d1df6fd8SDavid Lebrun 	if (slwt_a->desc->attrs != slwt_b->desc->attrs)
1078d1df6fd8SDavid Lebrun 		return 1;
1079d1df6fd8SDavid Lebrun 
1080d1df6fd8SDavid Lebrun 	for (i = 0; i < SEG6_LOCAL_MAX + 1; i++) {
1081d1df6fd8SDavid Lebrun 		if (slwt_a->desc->attrs & (1 << i)) {
1082d1df6fd8SDavid Lebrun 			param = &seg6_action_params[i];
1083d1df6fd8SDavid Lebrun 			if (param->cmp(slwt_a, slwt_b))
1084d1df6fd8SDavid Lebrun 				return 1;
1085d1df6fd8SDavid Lebrun 		}
1086d1df6fd8SDavid Lebrun 	}
1087d1df6fd8SDavid Lebrun 
1088d1df6fd8SDavid Lebrun 	return 0;
1089d1df6fd8SDavid Lebrun }
1090d1df6fd8SDavid Lebrun 
1091d1df6fd8SDavid Lebrun static const struct lwtunnel_encap_ops seg6_local_ops = {
1092d1df6fd8SDavid Lebrun 	.build_state	= seg6_local_build_state,
1093d1df6fd8SDavid Lebrun 	.destroy_state	= seg6_local_destroy_state,
1094d1df6fd8SDavid Lebrun 	.input		= seg6_local_input,
1095d1df6fd8SDavid Lebrun 	.fill_encap	= seg6_local_fill_encap,
1096d1df6fd8SDavid Lebrun 	.get_encap_size	= seg6_local_get_encap_size,
1097d1df6fd8SDavid Lebrun 	.cmp_encap	= seg6_local_cmp_encap,
1098d1df6fd8SDavid Lebrun 	.owner		= THIS_MODULE,
1099d1df6fd8SDavid Lebrun };
1100d1df6fd8SDavid Lebrun 
1101d1df6fd8SDavid Lebrun int __init seg6_local_init(void)
1102d1df6fd8SDavid Lebrun {
1103d1df6fd8SDavid Lebrun 	return lwtunnel_encap_add_ops(&seg6_local_ops,
1104d1df6fd8SDavid Lebrun 				      LWTUNNEL_ENCAP_SEG6_LOCAL);
1105d1df6fd8SDavid Lebrun }
1106d1df6fd8SDavid Lebrun 
1107d1df6fd8SDavid Lebrun void seg6_local_exit(void)
1108d1df6fd8SDavid Lebrun {
1109d1df6fd8SDavid Lebrun 	lwtunnel_encap_del_ops(&seg6_local_ops, LWTUNNEL_ENCAP_SEG6_LOCAL);
1110d1df6fd8SDavid Lebrun }
1111