xref: /openbmc/linux/net/ipv6/seg6_local.c (revision bb986a50421a11bf31a81afb15b9b8f45a4a3a11)
12874c5fdSThomas 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>
2662ebaeaeSYuki Taguchi #include <net/ip_tunnels.h>
27d1df6fd8SDavid Lebrun #ifdef CONFIG_IPV6_SEG6_HMAC
28d1df6fd8SDavid Lebrun #include <net/seg6_hmac.h>
29d1df6fd8SDavid Lebrun #endif
301c1e761eSMathieu Xhonneux #include <net/seg6_local.h>
31891ef8ddSDavid Lebrun #include <linux/etherdevice.h>
32004d4b27SMathieu Xhonneux #include <linux/bpf.h>
33d1df6fd8SDavid Lebrun 
34d1df6fd8SDavid Lebrun struct seg6_local_lwt;
35d1df6fd8SDavid Lebrun 
36d1df6fd8SDavid Lebrun struct seg6_action_desc {
37d1df6fd8SDavid Lebrun 	int action;
38d1df6fd8SDavid Lebrun 	unsigned long attrs;
39d1df6fd8SDavid Lebrun 	int (*input)(struct sk_buff *skb, struct seg6_local_lwt *slwt);
40d1df6fd8SDavid Lebrun 	int static_headroom;
41d1df6fd8SDavid Lebrun };
42d1df6fd8SDavid Lebrun 
43004d4b27SMathieu Xhonneux struct bpf_lwt_prog {
44004d4b27SMathieu Xhonneux 	struct bpf_prog *prog;
45004d4b27SMathieu Xhonneux 	char *name;
46004d4b27SMathieu Xhonneux };
47004d4b27SMathieu Xhonneux 
48d1df6fd8SDavid Lebrun struct seg6_local_lwt {
49d1df6fd8SDavid Lebrun 	int action;
50d1df6fd8SDavid Lebrun 	struct ipv6_sr_hdr *srh;
51d1df6fd8SDavid Lebrun 	int table;
52d1df6fd8SDavid Lebrun 	struct in_addr nh4;
53d1df6fd8SDavid Lebrun 	struct in6_addr nh6;
54d1df6fd8SDavid Lebrun 	int iif;
55d1df6fd8SDavid Lebrun 	int oif;
56004d4b27SMathieu Xhonneux 	struct bpf_lwt_prog bpf;
57d1df6fd8SDavid Lebrun 
58d1df6fd8SDavid Lebrun 	int headroom;
59d1df6fd8SDavid Lebrun 	struct seg6_action_desc *desc;
60d1df6fd8SDavid Lebrun };
61d1df6fd8SDavid Lebrun 
62d1df6fd8SDavid Lebrun static struct seg6_local_lwt *seg6_local_lwtunnel(struct lwtunnel_state *lwt)
63d1df6fd8SDavid Lebrun {
64d1df6fd8SDavid Lebrun 	return (struct seg6_local_lwt *)lwt->data;
65d1df6fd8SDavid Lebrun }
66d1df6fd8SDavid Lebrun 
67140f04c3SDavid Lebrun static struct ipv6_sr_hdr *get_srh(struct sk_buff *skb)
68140f04c3SDavid Lebrun {
69140f04c3SDavid Lebrun 	struct ipv6_sr_hdr *srh;
705829d70bSAhmed Abdelsalam 	int len, srhoff = 0;
71140f04c3SDavid Lebrun 
725829d70bSAhmed Abdelsalam 	if (ipv6_find_hdr(skb, &srhoff, IPPROTO_ROUTING, NULL, NULL) < 0)
73140f04c3SDavid Lebrun 		return NULL;
74140f04c3SDavid Lebrun 
755829d70bSAhmed Abdelsalam 	if (!pskb_may_pull(skb, srhoff + sizeof(*srh)))
765829d70bSAhmed Abdelsalam 		return NULL;
775829d70bSAhmed Abdelsalam 
785829d70bSAhmed Abdelsalam 	srh = (struct ipv6_sr_hdr *)(skb->data + srhoff);
795829d70bSAhmed Abdelsalam 
80140f04c3SDavid Lebrun 	len = (srh->hdrlen + 1) << 3;
81140f04c3SDavid Lebrun 
825829d70bSAhmed Abdelsalam 	if (!pskb_may_pull(skb, srhoff + len))
83140f04c3SDavid Lebrun 		return NULL;
84140f04c3SDavid Lebrun 
857f91ed8cSAndrea Mayer 	/* note that pskb_may_pull may change pointers in header;
867f91ed8cSAndrea Mayer 	 * for this reason it is necessary to reload them when needed.
877f91ed8cSAndrea Mayer 	 */
887f91ed8cSAndrea Mayer 	srh = (struct ipv6_sr_hdr *)(skb->data + srhoff);
897f91ed8cSAndrea Mayer 
90*bb986a50SAhmed Abdelsalam 	if (!seg6_validate_srh(srh, len, true))
91140f04c3SDavid Lebrun 		return NULL;
92140f04c3SDavid Lebrun 
93140f04c3SDavid Lebrun 	return srh;
94140f04c3SDavid Lebrun }
95140f04c3SDavid Lebrun 
96140f04c3SDavid Lebrun static struct ipv6_sr_hdr *get_and_validate_srh(struct sk_buff *skb)
97140f04c3SDavid Lebrun {
98140f04c3SDavid Lebrun 	struct ipv6_sr_hdr *srh;
99140f04c3SDavid Lebrun 
100140f04c3SDavid Lebrun 	srh = get_srh(skb);
101140f04c3SDavid Lebrun 	if (!srh)
102140f04c3SDavid Lebrun 		return NULL;
103140f04c3SDavid Lebrun 
104140f04c3SDavid Lebrun 	if (srh->segments_left == 0)
105140f04c3SDavid Lebrun 		return NULL;
106140f04c3SDavid Lebrun 
107140f04c3SDavid Lebrun #ifdef CONFIG_IPV6_SEG6_HMAC
108140f04c3SDavid Lebrun 	if (!seg6_hmac_validate_skb(skb))
109140f04c3SDavid Lebrun 		return NULL;
110140f04c3SDavid Lebrun #endif
111140f04c3SDavid Lebrun 
112140f04c3SDavid Lebrun 	return srh;
113140f04c3SDavid Lebrun }
114140f04c3SDavid Lebrun 
115d7a669ddSDavid Lebrun static bool decap_and_validate(struct sk_buff *skb, int proto)
116d7a669ddSDavid Lebrun {
117d7a669ddSDavid Lebrun 	struct ipv6_sr_hdr *srh;
118d7a669ddSDavid Lebrun 	unsigned int off = 0;
119d7a669ddSDavid Lebrun 
120d7a669ddSDavid Lebrun 	srh = get_srh(skb);
121d7a669ddSDavid Lebrun 	if (srh && srh->segments_left > 0)
122d7a669ddSDavid Lebrun 		return false;
123d7a669ddSDavid Lebrun 
124d7a669ddSDavid Lebrun #ifdef CONFIG_IPV6_SEG6_HMAC
125d7a669ddSDavid Lebrun 	if (srh && !seg6_hmac_validate_skb(skb))
126d7a669ddSDavid Lebrun 		return false;
127d7a669ddSDavid Lebrun #endif
128d7a669ddSDavid Lebrun 
129d7a669ddSDavid Lebrun 	if (ipv6_find_hdr(skb, &off, proto, NULL, NULL) < 0)
130d7a669ddSDavid Lebrun 		return false;
131d7a669ddSDavid Lebrun 
132d7a669ddSDavid Lebrun 	if (!pskb_pull(skb, off))
133d7a669ddSDavid Lebrun 		return false;
134d7a669ddSDavid Lebrun 
135d7a669ddSDavid Lebrun 	skb_postpull_rcsum(skb, skb_network_header(skb), off);
136d7a669ddSDavid Lebrun 
137d7a669ddSDavid Lebrun 	skb_reset_network_header(skb);
138d7a669ddSDavid Lebrun 	skb_reset_transport_header(skb);
13962ebaeaeSYuki Taguchi 	if (iptunnel_pull_offloads(skb))
14062ebaeaeSYuki Taguchi 		return false;
141d7a669ddSDavid Lebrun 
142d7a669ddSDavid Lebrun 	return true;
143d7a669ddSDavid Lebrun }
144d7a669ddSDavid Lebrun 
145d7a669ddSDavid Lebrun static void advance_nextseg(struct ipv6_sr_hdr *srh, struct in6_addr *daddr)
146d7a669ddSDavid Lebrun {
147d7a669ddSDavid Lebrun 	struct in6_addr *addr;
148d7a669ddSDavid Lebrun 
149d7a669ddSDavid Lebrun 	srh->segments_left--;
150d7a669ddSDavid Lebrun 	addr = srh->segments + srh->segments_left;
151d7a669ddSDavid Lebrun 	*daddr = *addr;
152d7a669ddSDavid Lebrun }
153d7a669ddSDavid Lebrun 
154fd1fef0cSAndrea Mayer static int
155fd1fef0cSAndrea Mayer seg6_lookup_any_nexthop(struct sk_buff *skb, struct in6_addr *nhaddr,
156fd1fef0cSAndrea Mayer 			u32 tbl_id, bool local_delivery)
157d7a669ddSDavid Lebrun {
158d7a669ddSDavid Lebrun 	struct net *net = dev_net(skb->dev);
159d7a669ddSDavid Lebrun 	struct ipv6hdr *hdr = ipv6_hdr(skb);
160d7a669ddSDavid Lebrun 	int flags = RT6_LOOKUP_F_HAS_SADDR;
161d7a669ddSDavid Lebrun 	struct dst_entry *dst = NULL;
162d7a669ddSDavid Lebrun 	struct rt6_info *rt;
163d7a669ddSDavid Lebrun 	struct flowi6 fl6;
164fd1fef0cSAndrea Mayer 	int dev_flags = 0;
165d7a669ddSDavid Lebrun 
166d7a669ddSDavid Lebrun 	fl6.flowi6_iif = skb->dev->ifindex;
167d7a669ddSDavid Lebrun 	fl6.daddr = nhaddr ? *nhaddr : hdr->daddr;
168d7a669ddSDavid Lebrun 	fl6.saddr = hdr->saddr;
169d7a669ddSDavid Lebrun 	fl6.flowlabel = ip6_flowinfo(hdr);
170d7a669ddSDavid Lebrun 	fl6.flowi6_mark = skb->mark;
171d7a669ddSDavid Lebrun 	fl6.flowi6_proto = hdr->nexthdr;
172d7a669ddSDavid Lebrun 
173d7a669ddSDavid Lebrun 	if (nhaddr)
174d7a669ddSDavid Lebrun 		fl6.flowi6_flags = FLOWI_FLAG_KNOWN_NH;
175d7a669ddSDavid Lebrun 
176d7a669ddSDavid Lebrun 	if (!tbl_id) {
177b75cc8f9SDavid Ahern 		dst = ip6_route_input_lookup(net, skb->dev, &fl6, skb, flags);
178d7a669ddSDavid Lebrun 	} else {
179d7a669ddSDavid Lebrun 		struct fib6_table *table;
180d7a669ddSDavid Lebrun 
181d7a669ddSDavid Lebrun 		table = fib6_get_table(net, tbl_id);
182d7a669ddSDavid Lebrun 		if (!table)
183d7a669ddSDavid Lebrun 			goto out;
184d7a669ddSDavid Lebrun 
185b75cc8f9SDavid Ahern 		rt = ip6_pol_route(net, table, 0, &fl6, skb, flags);
186d7a669ddSDavid Lebrun 		dst = &rt->dst;
187d7a669ddSDavid Lebrun 	}
188d7a669ddSDavid Lebrun 
189fd1fef0cSAndrea Mayer 	/* we want to discard traffic destined for local packet processing,
190fd1fef0cSAndrea Mayer 	 * if @local_delivery is set to false.
191fd1fef0cSAndrea Mayer 	 */
192fd1fef0cSAndrea Mayer 	if (!local_delivery)
193fd1fef0cSAndrea Mayer 		dev_flags |= IFF_LOOPBACK;
194fd1fef0cSAndrea Mayer 
195fd1fef0cSAndrea Mayer 	if (dst && (dst->dev->flags & dev_flags) && !dst->error) {
196d7a669ddSDavid Lebrun 		dst_release(dst);
197d7a669ddSDavid Lebrun 		dst = NULL;
198d7a669ddSDavid Lebrun 	}
199d7a669ddSDavid Lebrun 
200d7a669ddSDavid Lebrun out:
201d7a669ddSDavid Lebrun 	if (!dst) {
202d7a669ddSDavid Lebrun 		rt = net->ipv6.ip6_blk_hole_entry;
203d7a669ddSDavid Lebrun 		dst = &rt->dst;
204d7a669ddSDavid Lebrun 		dst_hold(dst);
205d7a669ddSDavid Lebrun 	}
206d7a669ddSDavid Lebrun 
207d7a669ddSDavid Lebrun 	skb_dst_drop(skb);
208d7a669ddSDavid Lebrun 	skb_dst_set(skb, dst);
2091c1e761eSMathieu Xhonneux 	return dst->error;
210d7a669ddSDavid Lebrun }
211d7a669ddSDavid Lebrun 
212fd1fef0cSAndrea Mayer int seg6_lookup_nexthop(struct sk_buff *skb,
213fd1fef0cSAndrea Mayer 			struct in6_addr *nhaddr, u32 tbl_id)
214fd1fef0cSAndrea Mayer {
215fd1fef0cSAndrea Mayer 	return seg6_lookup_any_nexthop(skb, nhaddr, tbl_id, false);
216fd1fef0cSAndrea Mayer }
217fd1fef0cSAndrea Mayer 
218140f04c3SDavid Lebrun /* regular endpoint function */
219140f04c3SDavid Lebrun static int input_action_end(struct sk_buff *skb, struct seg6_local_lwt *slwt)
220140f04c3SDavid Lebrun {
221140f04c3SDavid Lebrun 	struct ipv6_sr_hdr *srh;
222140f04c3SDavid Lebrun 
223140f04c3SDavid Lebrun 	srh = get_and_validate_srh(skb);
224140f04c3SDavid Lebrun 	if (!srh)
225140f04c3SDavid Lebrun 		goto drop;
226140f04c3SDavid Lebrun 
227d7a669ddSDavid Lebrun 	advance_nextseg(srh, &ipv6_hdr(skb)->daddr);
228140f04c3SDavid Lebrun 
2291c1e761eSMathieu Xhonneux 	seg6_lookup_nexthop(skb, NULL, 0);
230140f04c3SDavid Lebrun 
231140f04c3SDavid Lebrun 	return dst_input(skb);
232140f04c3SDavid Lebrun 
233140f04c3SDavid Lebrun drop:
234140f04c3SDavid Lebrun 	kfree_skb(skb);
235140f04c3SDavid Lebrun 	return -EINVAL;
236140f04c3SDavid Lebrun }
237140f04c3SDavid Lebrun 
238140f04c3SDavid Lebrun /* regular endpoint, and forward to specified nexthop */
239140f04c3SDavid Lebrun static int input_action_end_x(struct sk_buff *skb, struct seg6_local_lwt *slwt)
240140f04c3SDavid Lebrun {
241140f04c3SDavid Lebrun 	struct ipv6_sr_hdr *srh;
242140f04c3SDavid Lebrun 
243140f04c3SDavid Lebrun 	srh = get_and_validate_srh(skb);
244140f04c3SDavid Lebrun 	if (!srh)
245140f04c3SDavid Lebrun 		goto drop;
246140f04c3SDavid Lebrun 
247d7a669ddSDavid Lebrun 	advance_nextseg(srh, &ipv6_hdr(skb)->daddr);
248140f04c3SDavid Lebrun 
2491c1e761eSMathieu Xhonneux 	seg6_lookup_nexthop(skb, &slwt->nh6, 0);
250140f04c3SDavid Lebrun 
251140f04c3SDavid Lebrun 	return dst_input(skb);
252140f04c3SDavid Lebrun 
253140f04c3SDavid Lebrun drop:
254140f04c3SDavid Lebrun 	kfree_skb(skb);
255140f04c3SDavid Lebrun 	return -EINVAL;
256140f04c3SDavid Lebrun }
257140f04c3SDavid Lebrun 
258891ef8ddSDavid Lebrun static int input_action_end_t(struct sk_buff *skb, struct seg6_local_lwt *slwt)
259891ef8ddSDavid Lebrun {
260891ef8ddSDavid Lebrun 	struct ipv6_sr_hdr *srh;
261891ef8ddSDavid Lebrun 
262891ef8ddSDavid Lebrun 	srh = get_and_validate_srh(skb);
263891ef8ddSDavid Lebrun 	if (!srh)
264891ef8ddSDavid Lebrun 		goto drop;
265891ef8ddSDavid Lebrun 
266891ef8ddSDavid Lebrun 	advance_nextseg(srh, &ipv6_hdr(skb)->daddr);
267891ef8ddSDavid Lebrun 
2681c1e761eSMathieu Xhonneux 	seg6_lookup_nexthop(skb, NULL, slwt->table);
269891ef8ddSDavid Lebrun 
270891ef8ddSDavid Lebrun 	return dst_input(skb);
271891ef8ddSDavid Lebrun 
272891ef8ddSDavid Lebrun drop:
273891ef8ddSDavid Lebrun 	kfree_skb(skb);
274891ef8ddSDavid Lebrun 	return -EINVAL;
275891ef8ddSDavid Lebrun }
276891ef8ddSDavid Lebrun 
277891ef8ddSDavid Lebrun /* decapsulate and forward inner L2 frame on specified interface */
278891ef8ddSDavid Lebrun static int input_action_end_dx2(struct sk_buff *skb,
279891ef8ddSDavid Lebrun 				struct seg6_local_lwt *slwt)
280891ef8ddSDavid Lebrun {
281891ef8ddSDavid Lebrun 	struct net *net = dev_net(skb->dev);
282891ef8ddSDavid Lebrun 	struct net_device *odev;
283891ef8ddSDavid Lebrun 	struct ethhdr *eth;
284891ef8ddSDavid Lebrun 
28526776253SPaolo Lungaroni 	if (!decap_and_validate(skb, IPPROTO_ETHERNET))
286891ef8ddSDavid Lebrun 		goto drop;
287891ef8ddSDavid Lebrun 
288891ef8ddSDavid Lebrun 	if (!pskb_may_pull(skb, ETH_HLEN))
289891ef8ddSDavid Lebrun 		goto drop;
290891ef8ddSDavid Lebrun 
291891ef8ddSDavid Lebrun 	skb_reset_mac_header(skb);
292891ef8ddSDavid Lebrun 	eth = (struct ethhdr *)skb->data;
293891ef8ddSDavid Lebrun 
294891ef8ddSDavid Lebrun 	/* To determine the frame's protocol, we assume it is 802.3. This avoids
295891ef8ddSDavid Lebrun 	 * a call to eth_type_trans(), which is not really relevant for our
296891ef8ddSDavid Lebrun 	 * use case.
297891ef8ddSDavid Lebrun 	 */
298891ef8ddSDavid Lebrun 	if (!eth_proto_is_802_3(eth->h_proto))
299891ef8ddSDavid Lebrun 		goto drop;
300891ef8ddSDavid Lebrun 
301891ef8ddSDavid Lebrun 	odev = dev_get_by_index_rcu(net, slwt->oif);
302891ef8ddSDavid Lebrun 	if (!odev)
303891ef8ddSDavid Lebrun 		goto drop;
304891ef8ddSDavid Lebrun 
305891ef8ddSDavid Lebrun 	/* As we accept Ethernet frames, make sure the egress device is of
306891ef8ddSDavid Lebrun 	 * the correct type.
307891ef8ddSDavid Lebrun 	 */
308891ef8ddSDavid Lebrun 	if (odev->type != ARPHRD_ETHER)
309891ef8ddSDavid Lebrun 		goto drop;
310891ef8ddSDavid Lebrun 
311891ef8ddSDavid Lebrun 	if (!(odev->flags & IFF_UP) || !netif_carrier_ok(odev))
312891ef8ddSDavid Lebrun 		goto drop;
313891ef8ddSDavid Lebrun 
314891ef8ddSDavid Lebrun 	skb_orphan(skb);
315891ef8ddSDavid Lebrun 
316891ef8ddSDavid Lebrun 	if (skb_warn_if_lro(skb))
317891ef8ddSDavid Lebrun 		goto drop;
318891ef8ddSDavid Lebrun 
319891ef8ddSDavid Lebrun 	skb_forward_csum(skb);
320891ef8ddSDavid Lebrun 
321891ef8ddSDavid Lebrun 	if (skb->len - ETH_HLEN > odev->mtu)
322891ef8ddSDavid Lebrun 		goto drop;
323891ef8ddSDavid Lebrun 
324891ef8ddSDavid Lebrun 	skb->dev = odev;
325891ef8ddSDavid Lebrun 	skb->protocol = eth->h_proto;
326891ef8ddSDavid Lebrun 
327891ef8ddSDavid Lebrun 	return dev_queue_xmit(skb);
328891ef8ddSDavid Lebrun 
329891ef8ddSDavid Lebrun drop:
330891ef8ddSDavid Lebrun 	kfree_skb(skb);
331891ef8ddSDavid Lebrun 	return -EINVAL;
332891ef8ddSDavid Lebrun }
333891ef8ddSDavid Lebrun 
334140f04c3SDavid Lebrun /* decapsulate and forward to specified nexthop */
335140f04c3SDavid Lebrun static int input_action_end_dx6(struct sk_buff *skb,
336140f04c3SDavid Lebrun 				struct seg6_local_lwt *slwt)
337140f04c3SDavid Lebrun {
338d7a669ddSDavid Lebrun 	struct in6_addr *nhaddr = NULL;
339140f04c3SDavid Lebrun 
340140f04c3SDavid Lebrun 	/* this function accepts IPv6 encapsulated packets, with either
341140f04c3SDavid Lebrun 	 * an SRH with SL=0, or no SRH.
342140f04c3SDavid Lebrun 	 */
343140f04c3SDavid Lebrun 
344d7a669ddSDavid Lebrun 	if (!decap_and_validate(skb, IPPROTO_IPV6))
345140f04c3SDavid Lebrun 		goto drop;
346140f04c3SDavid Lebrun 
347d7a669ddSDavid Lebrun 	if (!pskb_may_pull(skb, sizeof(struct ipv6hdr)))
348140f04c3SDavid Lebrun 		goto drop;
349140f04c3SDavid Lebrun 
350140f04c3SDavid Lebrun 	/* The inner packet is not associated to any local interface,
351140f04c3SDavid Lebrun 	 * so we do not call netif_rx().
352140f04c3SDavid Lebrun 	 *
353140f04c3SDavid Lebrun 	 * If slwt->nh6 is set to ::, then lookup the nexthop for the
354140f04c3SDavid Lebrun 	 * inner packet's DA. Otherwise, use the specified nexthop.
355140f04c3SDavid Lebrun 	 */
356140f04c3SDavid Lebrun 
357d7a669ddSDavid Lebrun 	if (!ipv6_addr_any(&slwt->nh6))
358d7a669ddSDavid Lebrun 		nhaddr = &slwt->nh6;
359140f04c3SDavid Lebrun 
360c71644d0SAndrea Mayer 	skb_set_transport_header(skb, sizeof(struct ipv6hdr));
361c71644d0SAndrea Mayer 
3621c1e761eSMathieu Xhonneux 	seg6_lookup_nexthop(skb, nhaddr, 0);
363140f04c3SDavid Lebrun 
364140f04c3SDavid Lebrun 	return dst_input(skb);
365140f04c3SDavid Lebrun drop:
366140f04c3SDavid Lebrun 	kfree_skb(skb);
367140f04c3SDavid Lebrun 	return -EINVAL;
368140f04c3SDavid Lebrun }
369140f04c3SDavid Lebrun 
370891ef8ddSDavid Lebrun static int input_action_end_dx4(struct sk_buff *skb,
371891ef8ddSDavid Lebrun 				struct seg6_local_lwt *slwt)
372891ef8ddSDavid Lebrun {
373891ef8ddSDavid Lebrun 	struct iphdr *iph;
374891ef8ddSDavid Lebrun 	__be32 nhaddr;
375891ef8ddSDavid Lebrun 	int err;
376891ef8ddSDavid Lebrun 
377891ef8ddSDavid Lebrun 	if (!decap_and_validate(skb, IPPROTO_IPIP))
378891ef8ddSDavid Lebrun 		goto drop;
379891ef8ddSDavid Lebrun 
380891ef8ddSDavid Lebrun 	if (!pskb_may_pull(skb, sizeof(struct iphdr)))
381891ef8ddSDavid Lebrun 		goto drop;
382891ef8ddSDavid Lebrun 
383891ef8ddSDavid Lebrun 	skb->protocol = htons(ETH_P_IP);
384891ef8ddSDavid Lebrun 
385891ef8ddSDavid Lebrun 	iph = ip_hdr(skb);
386891ef8ddSDavid Lebrun 
387891ef8ddSDavid Lebrun 	nhaddr = slwt->nh4.s_addr ?: iph->daddr;
388891ef8ddSDavid Lebrun 
389891ef8ddSDavid Lebrun 	skb_dst_drop(skb);
390891ef8ddSDavid Lebrun 
391c71644d0SAndrea Mayer 	skb_set_transport_header(skb, sizeof(struct iphdr));
392c71644d0SAndrea Mayer 
393891ef8ddSDavid Lebrun 	err = ip_route_input(skb, nhaddr, iph->saddr, 0, skb->dev);
394891ef8ddSDavid Lebrun 	if (err)
395891ef8ddSDavid Lebrun 		goto drop;
396891ef8ddSDavid Lebrun 
397891ef8ddSDavid Lebrun 	return dst_input(skb);
398891ef8ddSDavid Lebrun 
399891ef8ddSDavid Lebrun drop:
400891ef8ddSDavid Lebrun 	kfree_skb(skb);
401891ef8ddSDavid Lebrun 	return -EINVAL;
402891ef8ddSDavid Lebrun }
403891ef8ddSDavid Lebrun 
404891ef8ddSDavid Lebrun static int input_action_end_dt6(struct sk_buff *skb,
405891ef8ddSDavid Lebrun 				struct seg6_local_lwt *slwt)
406891ef8ddSDavid Lebrun {
407891ef8ddSDavid Lebrun 	if (!decap_and_validate(skb, IPPROTO_IPV6))
408891ef8ddSDavid Lebrun 		goto drop;
409891ef8ddSDavid Lebrun 
410891ef8ddSDavid Lebrun 	if (!pskb_may_pull(skb, sizeof(struct ipv6hdr)))
411891ef8ddSDavid Lebrun 		goto drop;
412891ef8ddSDavid Lebrun 
413c71644d0SAndrea Mayer 	skb_set_transport_header(skb, sizeof(struct ipv6hdr));
414c71644d0SAndrea Mayer 
415fd1fef0cSAndrea Mayer 	seg6_lookup_any_nexthop(skb, NULL, slwt->table, true);
416891ef8ddSDavid Lebrun 
417891ef8ddSDavid Lebrun 	return dst_input(skb);
418891ef8ddSDavid Lebrun 
419891ef8ddSDavid Lebrun drop:
420891ef8ddSDavid Lebrun 	kfree_skb(skb);
421891ef8ddSDavid Lebrun 	return -EINVAL;
422891ef8ddSDavid Lebrun }
423891ef8ddSDavid Lebrun 
424140f04c3SDavid Lebrun /* push an SRH on top of the current one */
425140f04c3SDavid Lebrun static int input_action_end_b6(struct sk_buff *skb, 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 
434140f04c3SDavid Lebrun 	err = seg6_do_srh_inline(skb, slwt->srh);
435140f04c3SDavid Lebrun 	if (err)
436140f04c3SDavid Lebrun 		goto drop;
437140f04c3SDavid Lebrun 
438140f04c3SDavid Lebrun 	ipv6_hdr(skb)->payload_len = htons(skb->len - sizeof(struct ipv6hdr));
439140f04c3SDavid Lebrun 	skb_set_transport_header(skb, sizeof(struct ipv6hdr));
440140f04c3SDavid Lebrun 
4411c1e761eSMathieu Xhonneux 	seg6_lookup_nexthop(skb, NULL, 0);
442140f04c3SDavid Lebrun 
443140f04c3SDavid Lebrun 	return dst_input(skb);
444140f04c3SDavid Lebrun 
445140f04c3SDavid Lebrun drop:
446140f04c3SDavid Lebrun 	kfree_skb(skb);
447140f04c3SDavid Lebrun 	return err;
448140f04c3SDavid Lebrun }
449140f04c3SDavid Lebrun 
450140f04c3SDavid Lebrun /* encapsulate within an outer IPv6 header and a specified SRH */
451140f04c3SDavid Lebrun static int input_action_end_b6_encap(struct sk_buff *skb,
452140f04c3SDavid Lebrun 				     struct seg6_local_lwt *slwt)
453140f04c3SDavid Lebrun {
454140f04c3SDavid Lebrun 	struct ipv6_sr_hdr *srh;
455140f04c3SDavid Lebrun 	int err = -EINVAL;
456140f04c3SDavid Lebrun 
457140f04c3SDavid Lebrun 	srh = get_and_validate_srh(skb);
458140f04c3SDavid Lebrun 	if (!srh)
459140f04c3SDavid Lebrun 		goto drop;
460140f04c3SDavid Lebrun 
461d7a669ddSDavid Lebrun 	advance_nextseg(srh, &ipv6_hdr(skb)->daddr);
462140f04c3SDavid Lebrun 
463140f04c3SDavid Lebrun 	skb_reset_inner_headers(skb);
464140f04c3SDavid Lebrun 	skb->encapsulation = 1;
465140f04c3SDavid Lebrun 
46632d99d0bSDavid Lebrun 	err = seg6_do_srh_encap(skb, slwt->srh, IPPROTO_IPV6);
467140f04c3SDavid Lebrun 	if (err)
468140f04c3SDavid Lebrun 		goto drop;
469140f04c3SDavid Lebrun 
470140f04c3SDavid Lebrun 	ipv6_hdr(skb)->payload_len = htons(skb->len - sizeof(struct ipv6hdr));
471140f04c3SDavid Lebrun 	skb_set_transport_header(skb, sizeof(struct ipv6hdr));
472140f04c3SDavid Lebrun 
4731c1e761eSMathieu Xhonneux 	seg6_lookup_nexthop(skb, NULL, 0);
474140f04c3SDavid Lebrun 
475140f04c3SDavid Lebrun 	return dst_input(skb);
476140f04c3SDavid Lebrun 
477140f04c3SDavid Lebrun drop:
478140f04c3SDavid Lebrun 	kfree_skb(skb);
479140f04c3SDavid Lebrun 	return err;
480140f04c3SDavid Lebrun }
481140f04c3SDavid Lebrun 
482fe94cc29SMathieu Xhonneux DEFINE_PER_CPU(struct seg6_bpf_srh_state, seg6_bpf_srh_states);
483fe94cc29SMathieu Xhonneux 
484486cdf21SMathieu Xhonneux bool seg6_bpf_has_valid_srh(struct sk_buff *skb)
485486cdf21SMathieu Xhonneux {
486486cdf21SMathieu Xhonneux 	struct seg6_bpf_srh_state *srh_state =
487486cdf21SMathieu Xhonneux 		this_cpu_ptr(&seg6_bpf_srh_states);
488486cdf21SMathieu Xhonneux 	struct ipv6_sr_hdr *srh = srh_state->srh;
489486cdf21SMathieu Xhonneux 
490486cdf21SMathieu Xhonneux 	if (unlikely(srh == NULL))
491486cdf21SMathieu Xhonneux 		return false;
492486cdf21SMathieu Xhonneux 
493486cdf21SMathieu Xhonneux 	if (unlikely(!srh_state->valid)) {
494486cdf21SMathieu Xhonneux 		if ((srh_state->hdrlen & 7) != 0)
495486cdf21SMathieu Xhonneux 			return false;
496486cdf21SMathieu Xhonneux 
497486cdf21SMathieu Xhonneux 		srh->hdrlen = (u8)(srh_state->hdrlen >> 3);
498*bb986a50SAhmed Abdelsalam 		if (!seg6_validate_srh(srh, (srh->hdrlen + 1) << 3, true))
499486cdf21SMathieu Xhonneux 			return false;
500486cdf21SMathieu Xhonneux 
501486cdf21SMathieu Xhonneux 		srh_state->valid = true;
502486cdf21SMathieu Xhonneux 	}
503486cdf21SMathieu Xhonneux 
504486cdf21SMathieu Xhonneux 	return true;
505486cdf21SMathieu Xhonneux }
506486cdf21SMathieu Xhonneux 
507004d4b27SMathieu Xhonneux static int input_action_end_bpf(struct sk_buff *skb,
508004d4b27SMathieu Xhonneux 				struct seg6_local_lwt *slwt)
509004d4b27SMathieu Xhonneux {
510004d4b27SMathieu Xhonneux 	struct seg6_bpf_srh_state *srh_state =
511004d4b27SMathieu Xhonneux 		this_cpu_ptr(&seg6_bpf_srh_states);
512004d4b27SMathieu Xhonneux 	struct ipv6_sr_hdr *srh;
513004d4b27SMathieu Xhonneux 	int ret;
514004d4b27SMathieu Xhonneux 
515004d4b27SMathieu Xhonneux 	srh = get_and_validate_srh(skb);
516486cdf21SMathieu Xhonneux 	if (!srh) {
517486cdf21SMathieu Xhonneux 		kfree_skb(skb);
518486cdf21SMathieu Xhonneux 		return -EINVAL;
519486cdf21SMathieu Xhonneux 	}
520004d4b27SMathieu Xhonneux 	advance_nextseg(srh, &ipv6_hdr(skb)->daddr);
521004d4b27SMathieu Xhonneux 
522004d4b27SMathieu Xhonneux 	/* preempt_disable is needed to protect the per-CPU buffer srh_state,
523004d4b27SMathieu Xhonneux 	 * which is also accessed by the bpf_lwt_seg6_* helpers
524004d4b27SMathieu Xhonneux 	 */
525004d4b27SMathieu Xhonneux 	preempt_disable();
526486cdf21SMathieu Xhonneux 	srh_state->srh = srh;
527004d4b27SMathieu Xhonneux 	srh_state->hdrlen = srh->hdrlen << 3;
528486cdf21SMathieu Xhonneux 	srh_state->valid = true;
529004d4b27SMathieu Xhonneux 
530004d4b27SMathieu Xhonneux 	rcu_read_lock();
531004d4b27SMathieu Xhonneux 	bpf_compute_data_pointers(skb);
532004d4b27SMathieu Xhonneux 	ret = bpf_prog_run_save_cb(slwt->bpf.prog, skb);
533004d4b27SMathieu Xhonneux 	rcu_read_unlock();
534004d4b27SMathieu Xhonneux 
535004d4b27SMathieu Xhonneux 	switch (ret) {
536004d4b27SMathieu Xhonneux 	case BPF_OK:
537004d4b27SMathieu Xhonneux 	case BPF_REDIRECT:
538004d4b27SMathieu Xhonneux 		break;
539004d4b27SMathieu Xhonneux 	case BPF_DROP:
540004d4b27SMathieu Xhonneux 		goto drop;
541004d4b27SMathieu Xhonneux 	default:
542004d4b27SMathieu Xhonneux 		pr_warn_once("bpf-seg6local: Illegal return value %u\n", ret);
543004d4b27SMathieu Xhonneux 		goto drop;
544004d4b27SMathieu Xhonneux 	}
545004d4b27SMathieu Xhonneux 
546486cdf21SMathieu Xhonneux 	if (srh_state->srh && !seg6_bpf_has_valid_srh(skb))
547004d4b27SMathieu Xhonneux 		goto drop;
548004d4b27SMathieu Xhonneux 
549486cdf21SMathieu Xhonneux 	preempt_enable();
550004d4b27SMathieu Xhonneux 	if (ret != BPF_REDIRECT)
551004d4b27SMathieu Xhonneux 		seg6_lookup_nexthop(skb, NULL, 0);
552004d4b27SMathieu Xhonneux 
553004d4b27SMathieu Xhonneux 	return dst_input(skb);
554004d4b27SMathieu Xhonneux 
555004d4b27SMathieu Xhonneux drop:
556486cdf21SMathieu Xhonneux 	preempt_enable();
557004d4b27SMathieu Xhonneux 	kfree_skb(skb);
558004d4b27SMathieu Xhonneux 	return -EINVAL;
559004d4b27SMathieu Xhonneux }
560004d4b27SMathieu Xhonneux 
561d1df6fd8SDavid Lebrun static struct seg6_action_desc seg6_action_table[] = {
562d1df6fd8SDavid Lebrun 	{
563d1df6fd8SDavid Lebrun 		.action		= SEG6_LOCAL_ACTION_END,
564d1df6fd8SDavid Lebrun 		.attrs		= 0,
565140f04c3SDavid Lebrun 		.input		= input_action_end,
566d1df6fd8SDavid Lebrun 	},
567140f04c3SDavid Lebrun 	{
568140f04c3SDavid Lebrun 		.action		= SEG6_LOCAL_ACTION_END_X,
569140f04c3SDavid Lebrun 		.attrs		= (1 << SEG6_LOCAL_NH6),
570140f04c3SDavid Lebrun 		.input		= input_action_end_x,
571140f04c3SDavid Lebrun 	},
572140f04c3SDavid Lebrun 	{
573891ef8ddSDavid Lebrun 		.action		= SEG6_LOCAL_ACTION_END_T,
574891ef8ddSDavid Lebrun 		.attrs		= (1 << SEG6_LOCAL_TABLE),
575891ef8ddSDavid Lebrun 		.input		= input_action_end_t,
576891ef8ddSDavid Lebrun 	},
577891ef8ddSDavid Lebrun 	{
578891ef8ddSDavid Lebrun 		.action		= SEG6_LOCAL_ACTION_END_DX2,
579891ef8ddSDavid Lebrun 		.attrs		= (1 << SEG6_LOCAL_OIF),
580891ef8ddSDavid Lebrun 		.input		= input_action_end_dx2,
581891ef8ddSDavid Lebrun 	},
582891ef8ddSDavid Lebrun 	{
583140f04c3SDavid Lebrun 		.action		= SEG6_LOCAL_ACTION_END_DX6,
584140f04c3SDavid Lebrun 		.attrs		= (1 << SEG6_LOCAL_NH6),
585140f04c3SDavid Lebrun 		.input		= input_action_end_dx6,
586140f04c3SDavid Lebrun 	},
587140f04c3SDavid Lebrun 	{
588891ef8ddSDavid Lebrun 		.action		= SEG6_LOCAL_ACTION_END_DX4,
589891ef8ddSDavid Lebrun 		.attrs		= (1 << SEG6_LOCAL_NH4),
590891ef8ddSDavid Lebrun 		.input		= input_action_end_dx4,
591891ef8ddSDavid Lebrun 	},
592891ef8ddSDavid Lebrun 	{
593891ef8ddSDavid Lebrun 		.action		= SEG6_LOCAL_ACTION_END_DT6,
594891ef8ddSDavid Lebrun 		.attrs		= (1 << SEG6_LOCAL_TABLE),
595891ef8ddSDavid Lebrun 		.input		= input_action_end_dt6,
596891ef8ddSDavid Lebrun 	},
597891ef8ddSDavid Lebrun 	{
598140f04c3SDavid Lebrun 		.action		= SEG6_LOCAL_ACTION_END_B6,
599140f04c3SDavid Lebrun 		.attrs		= (1 << SEG6_LOCAL_SRH),
600140f04c3SDavid Lebrun 		.input		= input_action_end_b6,
601140f04c3SDavid Lebrun 	},
602140f04c3SDavid Lebrun 	{
603140f04c3SDavid Lebrun 		.action		= SEG6_LOCAL_ACTION_END_B6_ENCAP,
604140f04c3SDavid Lebrun 		.attrs		= (1 << SEG6_LOCAL_SRH),
605140f04c3SDavid Lebrun 		.input		= input_action_end_b6_encap,
606140f04c3SDavid Lebrun 		.static_headroom	= sizeof(struct ipv6hdr),
607004d4b27SMathieu Xhonneux 	},
608004d4b27SMathieu Xhonneux 	{
609004d4b27SMathieu Xhonneux 		.action		= SEG6_LOCAL_ACTION_END_BPF,
610004d4b27SMathieu Xhonneux 		.attrs		= (1 << SEG6_LOCAL_BPF),
611004d4b27SMathieu Xhonneux 		.input		= input_action_end_bpf,
612004d4b27SMathieu Xhonneux 	},
613004d4b27SMathieu Xhonneux 
614d1df6fd8SDavid Lebrun };
615d1df6fd8SDavid Lebrun 
616d1df6fd8SDavid Lebrun static struct seg6_action_desc *__get_action_desc(int action)
617d1df6fd8SDavid Lebrun {
618d1df6fd8SDavid Lebrun 	struct seg6_action_desc *desc;
619d1df6fd8SDavid Lebrun 	int i, count;
620d1df6fd8SDavid Lebrun 
621709af180SColin Ian King 	count = ARRAY_SIZE(seg6_action_table);
622d1df6fd8SDavid Lebrun 	for (i = 0; i < count; i++) {
623d1df6fd8SDavid Lebrun 		desc = &seg6_action_table[i];
624d1df6fd8SDavid Lebrun 		if (desc->action == action)
625d1df6fd8SDavid Lebrun 			return desc;
626d1df6fd8SDavid Lebrun 	}
627d1df6fd8SDavid Lebrun 
628d1df6fd8SDavid Lebrun 	return NULL;
629d1df6fd8SDavid Lebrun }
630d1df6fd8SDavid Lebrun 
631d1df6fd8SDavid Lebrun static int seg6_local_input(struct sk_buff *skb)
632d1df6fd8SDavid Lebrun {
633d1df6fd8SDavid Lebrun 	struct dst_entry *orig_dst = skb_dst(skb);
634d1df6fd8SDavid Lebrun 	struct seg6_action_desc *desc;
635d1df6fd8SDavid Lebrun 	struct seg6_local_lwt *slwt;
636d1df6fd8SDavid Lebrun 
6376285217fSDavid Lebrun 	if (skb->protocol != htons(ETH_P_IPV6)) {
6386285217fSDavid Lebrun 		kfree_skb(skb);
6396285217fSDavid Lebrun 		return -EINVAL;
6406285217fSDavid Lebrun 	}
6416285217fSDavid Lebrun 
642d1df6fd8SDavid Lebrun 	slwt = seg6_local_lwtunnel(orig_dst->lwtstate);
643d1df6fd8SDavid Lebrun 	desc = slwt->desc;
644d1df6fd8SDavid Lebrun 
645d1df6fd8SDavid Lebrun 	return desc->input(skb, slwt);
646d1df6fd8SDavid Lebrun }
647d1df6fd8SDavid Lebrun 
648d1df6fd8SDavid Lebrun static const struct nla_policy seg6_local_policy[SEG6_LOCAL_MAX + 1] = {
649d1df6fd8SDavid Lebrun 	[SEG6_LOCAL_ACTION]	= { .type = NLA_U32 },
650d1df6fd8SDavid Lebrun 	[SEG6_LOCAL_SRH]	= { .type = NLA_BINARY },
651d1df6fd8SDavid Lebrun 	[SEG6_LOCAL_TABLE]	= { .type = NLA_U32 },
652d1df6fd8SDavid Lebrun 	[SEG6_LOCAL_NH4]	= { .type = NLA_BINARY,
653d1df6fd8SDavid Lebrun 				    .len = sizeof(struct in_addr) },
654d1df6fd8SDavid Lebrun 	[SEG6_LOCAL_NH6]	= { .type = NLA_BINARY,
655d1df6fd8SDavid Lebrun 				    .len = sizeof(struct in6_addr) },
656d1df6fd8SDavid Lebrun 	[SEG6_LOCAL_IIF]	= { .type = NLA_U32 },
657d1df6fd8SDavid Lebrun 	[SEG6_LOCAL_OIF]	= { .type = NLA_U32 },
658004d4b27SMathieu Xhonneux 	[SEG6_LOCAL_BPF]	= { .type = NLA_NESTED },
659d1df6fd8SDavid Lebrun };
660d1df6fd8SDavid Lebrun 
6612d9cc60aSDavid Lebrun static int parse_nla_srh(struct nlattr **attrs, struct seg6_local_lwt *slwt)
6622d9cc60aSDavid Lebrun {
6632d9cc60aSDavid Lebrun 	struct ipv6_sr_hdr *srh;
6642d9cc60aSDavid Lebrun 	int len;
6652d9cc60aSDavid Lebrun 
6662d9cc60aSDavid Lebrun 	srh = nla_data(attrs[SEG6_LOCAL_SRH]);
6672d9cc60aSDavid Lebrun 	len = nla_len(attrs[SEG6_LOCAL_SRH]);
6682d9cc60aSDavid Lebrun 
6692d9cc60aSDavid Lebrun 	/* SRH must contain at least one segment */
6702d9cc60aSDavid Lebrun 	if (len < sizeof(*srh) + sizeof(struct in6_addr))
6712d9cc60aSDavid Lebrun 		return -EINVAL;
6722d9cc60aSDavid Lebrun 
673*bb986a50SAhmed Abdelsalam 	if (!seg6_validate_srh(srh, len, false))
6742d9cc60aSDavid Lebrun 		return -EINVAL;
6752d9cc60aSDavid Lebrun 
6767fa41efaSYueHaibing 	slwt->srh = kmemdup(srh, len, GFP_KERNEL);
6772d9cc60aSDavid Lebrun 	if (!slwt->srh)
6782d9cc60aSDavid Lebrun 		return -ENOMEM;
6792d9cc60aSDavid Lebrun 
6802d9cc60aSDavid Lebrun 	slwt->headroom += len;
6812d9cc60aSDavid Lebrun 
6822d9cc60aSDavid Lebrun 	return 0;
6832d9cc60aSDavid Lebrun }
6842d9cc60aSDavid Lebrun 
6852d9cc60aSDavid Lebrun static int put_nla_srh(struct sk_buff *skb, struct seg6_local_lwt *slwt)
6862d9cc60aSDavid Lebrun {
6872d9cc60aSDavid Lebrun 	struct ipv6_sr_hdr *srh;
6882d9cc60aSDavid Lebrun 	struct nlattr *nla;
6892d9cc60aSDavid Lebrun 	int len;
6902d9cc60aSDavid Lebrun 
6912d9cc60aSDavid Lebrun 	srh = slwt->srh;
6922d9cc60aSDavid Lebrun 	len = (srh->hdrlen + 1) << 3;
6932d9cc60aSDavid Lebrun 
6942d9cc60aSDavid Lebrun 	nla = nla_reserve(skb, SEG6_LOCAL_SRH, len);
6952d9cc60aSDavid Lebrun 	if (!nla)
6962d9cc60aSDavid Lebrun 		return -EMSGSIZE;
6972d9cc60aSDavid Lebrun 
6982d9cc60aSDavid Lebrun 	memcpy(nla_data(nla), srh, len);
6992d9cc60aSDavid Lebrun 
7002d9cc60aSDavid Lebrun 	return 0;
7012d9cc60aSDavid Lebrun }
7022d9cc60aSDavid Lebrun 
7032d9cc60aSDavid Lebrun static int cmp_nla_srh(struct seg6_local_lwt *a, struct seg6_local_lwt *b)
7042d9cc60aSDavid Lebrun {
7052d9cc60aSDavid Lebrun 	int len = (a->srh->hdrlen + 1) << 3;
7062d9cc60aSDavid Lebrun 
7072d9cc60aSDavid Lebrun 	if (len != ((b->srh->hdrlen + 1) << 3))
7082d9cc60aSDavid Lebrun 		return 1;
7092d9cc60aSDavid Lebrun 
7102d9cc60aSDavid Lebrun 	return memcmp(a->srh, b->srh, len);
7112d9cc60aSDavid Lebrun }
7122d9cc60aSDavid Lebrun 
7132d9cc60aSDavid Lebrun static int parse_nla_table(struct nlattr **attrs, struct seg6_local_lwt *slwt)
7142d9cc60aSDavid Lebrun {
7152d9cc60aSDavid Lebrun 	slwt->table = nla_get_u32(attrs[SEG6_LOCAL_TABLE]);
7162d9cc60aSDavid Lebrun 
7172d9cc60aSDavid Lebrun 	return 0;
7182d9cc60aSDavid Lebrun }
7192d9cc60aSDavid Lebrun 
7202d9cc60aSDavid Lebrun static int put_nla_table(struct sk_buff *skb, struct seg6_local_lwt *slwt)
7212d9cc60aSDavid Lebrun {
7222d9cc60aSDavid Lebrun 	if (nla_put_u32(skb, SEG6_LOCAL_TABLE, slwt->table))
7232d9cc60aSDavid Lebrun 		return -EMSGSIZE;
7242d9cc60aSDavid Lebrun 
7252d9cc60aSDavid Lebrun 	return 0;
7262d9cc60aSDavid Lebrun }
7272d9cc60aSDavid Lebrun 
7282d9cc60aSDavid Lebrun static int cmp_nla_table(struct seg6_local_lwt *a, struct seg6_local_lwt *b)
7292d9cc60aSDavid Lebrun {
7302d9cc60aSDavid Lebrun 	if (a->table != b->table)
7312d9cc60aSDavid Lebrun 		return 1;
7322d9cc60aSDavid Lebrun 
7332d9cc60aSDavid Lebrun 	return 0;
7342d9cc60aSDavid Lebrun }
7352d9cc60aSDavid Lebrun 
7362d9cc60aSDavid Lebrun static int parse_nla_nh4(struct nlattr **attrs, struct seg6_local_lwt *slwt)
7372d9cc60aSDavid Lebrun {
7382d9cc60aSDavid Lebrun 	memcpy(&slwt->nh4, nla_data(attrs[SEG6_LOCAL_NH4]),
7392d9cc60aSDavid Lebrun 	       sizeof(struct in_addr));
7402d9cc60aSDavid Lebrun 
7412d9cc60aSDavid Lebrun 	return 0;
7422d9cc60aSDavid Lebrun }
7432d9cc60aSDavid Lebrun 
7442d9cc60aSDavid Lebrun static int put_nla_nh4(struct sk_buff *skb, struct seg6_local_lwt *slwt)
7452d9cc60aSDavid Lebrun {
7462d9cc60aSDavid Lebrun 	struct nlattr *nla;
7472d9cc60aSDavid Lebrun 
7482d9cc60aSDavid Lebrun 	nla = nla_reserve(skb, SEG6_LOCAL_NH4, sizeof(struct in_addr));
7492d9cc60aSDavid Lebrun 	if (!nla)
7502d9cc60aSDavid Lebrun 		return -EMSGSIZE;
7512d9cc60aSDavid Lebrun 
7522d9cc60aSDavid Lebrun 	memcpy(nla_data(nla), &slwt->nh4, sizeof(struct in_addr));
7532d9cc60aSDavid Lebrun 
7542d9cc60aSDavid Lebrun 	return 0;
7552d9cc60aSDavid Lebrun }
7562d9cc60aSDavid Lebrun 
7572d9cc60aSDavid Lebrun static int cmp_nla_nh4(struct seg6_local_lwt *a, struct seg6_local_lwt *b)
7582d9cc60aSDavid Lebrun {
7592d9cc60aSDavid Lebrun 	return memcmp(&a->nh4, &b->nh4, sizeof(struct in_addr));
7602d9cc60aSDavid Lebrun }
7612d9cc60aSDavid Lebrun 
7622d9cc60aSDavid Lebrun static int parse_nla_nh6(struct nlattr **attrs, struct seg6_local_lwt *slwt)
7632d9cc60aSDavid Lebrun {
7642d9cc60aSDavid Lebrun 	memcpy(&slwt->nh6, nla_data(attrs[SEG6_LOCAL_NH6]),
7652d9cc60aSDavid Lebrun 	       sizeof(struct in6_addr));
7662d9cc60aSDavid Lebrun 
7672d9cc60aSDavid Lebrun 	return 0;
7682d9cc60aSDavid Lebrun }
7692d9cc60aSDavid Lebrun 
7702d9cc60aSDavid Lebrun static int put_nla_nh6(struct sk_buff *skb, struct seg6_local_lwt *slwt)
7712d9cc60aSDavid Lebrun {
7722d9cc60aSDavid Lebrun 	struct nlattr *nla;
7732d9cc60aSDavid Lebrun 
7742d9cc60aSDavid Lebrun 	nla = nla_reserve(skb, SEG6_LOCAL_NH6, sizeof(struct in6_addr));
7752d9cc60aSDavid Lebrun 	if (!nla)
7762d9cc60aSDavid Lebrun 		return -EMSGSIZE;
7772d9cc60aSDavid Lebrun 
7782d9cc60aSDavid Lebrun 	memcpy(nla_data(nla), &slwt->nh6, sizeof(struct in6_addr));
7792d9cc60aSDavid Lebrun 
7802d9cc60aSDavid Lebrun 	return 0;
7812d9cc60aSDavid Lebrun }
7822d9cc60aSDavid Lebrun 
7832d9cc60aSDavid Lebrun static int cmp_nla_nh6(struct seg6_local_lwt *a, struct seg6_local_lwt *b)
7842d9cc60aSDavid Lebrun {
7852d9cc60aSDavid Lebrun 	return memcmp(&a->nh6, &b->nh6, sizeof(struct in6_addr));
7862d9cc60aSDavid Lebrun }
7872d9cc60aSDavid Lebrun 
7882d9cc60aSDavid Lebrun static int parse_nla_iif(struct nlattr **attrs, struct seg6_local_lwt *slwt)
7892d9cc60aSDavid Lebrun {
7902d9cc60aSDavid Lebrun 	slwt->iif = nla_get_u32(attrs[SEG6_LOCAL_IIF]);
7912d9cc60aSDavid Lebrun 
7922d9cc60aSDavid Lebrun 	return 0;
7932d9cc60aSDavid Lebrun }
7942d9cc60aSDavid Lebrun 
7952d9cc60aSDavid Lebrun static int put_nla_iif(struct sk_buff *skb, struct seg6_local_lwt *slwt)
7962d9cc60aSDavid Lebrun {
7972d9cc60aSDavid Lebrun 	if (nla_put_u32(skb, SEG6_LOCAL_IIF, slwt->iif))
7982d9cc60aSDavid Lebrun 		return -EMSGSIZE;
7992d9cc60aSDavid Lebrun 
8002d9cc60aSDavid Lebrun 	return 0;
8012d9cc60aSDavid Lebrun }
8022d9cc60aSDavid Lebrun 
8032d9cc60aSDavid Lebrun static int cmp_nla_iif(struct seg6_local_lwt *a, struct seg6_local_lwt *b)
8042d9cc60aSDavid Lebrun {
8052d9cc60aSDavid Lebrun 	if (a->iif != b->iif)
8062d9cc60aSDavid Lebrun 		return 1;
8072d9cc60aSDavid Lebrun 
8082d9cc60aSDavid Lebrun 	return 0;
8092d9cc60aSDavid Lebrun }
8102d9cc60aSDavid Lebrun 
8112d9cc60aSDavid Lebrun static int parse_nla_oif(struct nlattr **attrs, struct seg6_local_lwt *slwt)
8122d9cc60aSDavid Lebrun {
8132d9cc60aSDavid Lebrun 	slwt->oif = nla_get_u32(attrs[SEG6_LOCAL_OIF]);
8142d9cc60aSDavid Lebrun 
8152d9cc60aSDavid Lebrun 	return 0;
8162d9cc60aSDavid Lebrun }
8172d9cc60aSDavid Lebrun 
8182d9cc60aSDavid Lebrun static int put_nla_oif(struct sk_buff *skb, struct seg6_local_lwt *slwt)
8192d9cc60aSDavid Lebrun {
8202d9cc60aSDavid Lebrun 	if (nla_put_u32(skb, SEG6_LOCAL_OIF, slwt->oif))
8212d9cc60aSDavid Lebrun 		return -EMSGSIZE;
8222d9cc60aSDavid Lebrun 
8232d9cc60aSDavid Lebrun 	return 0;
8242d9cc60aSDavid Lebrun }
8252d9cc60aSDavid Lebrun 
8262d9cc60aSDavid Lebrun static int cmp_nla_oif(struct seg6_local_lwt *a, struct seg6_local_lwt *b)
8272d9cc60aSDavid Lebrun {
8282d9cc60aSDavid Lebrun 	if (a->oif != b->oif)
8292d9cc60aSDavid Lebrun 		return 1;
8302d9cc60aSDavid Lebrun 
8312d9cc60aSDavid Lebrun 	return 0;
8322d9cc60aSDavid Lebrun }
8332d9cc60aSDavid Lebrun 
834004d4b27SMathieu Xhonneux #define MAX_PROG_NAME 256
835004d4b27SMathieu Xhonneux static const struct nla_policy bpf_prog_policy[SEG6_LOCAL_BPF_PROG_MAX + 1] = {
836004d4b27SMathieu Xhonneux 	[SEG6_LOCAL_BPF_PROG]	   = { .type = NLA_U32, },
837004d4b27SMathieu Xhonneux 	[SEG6_LOCAL_BPF_PROG_NAME] = { .type = NLA_NUL_STRING,
838004d4b27SMathieu Xhonneux 				       .len = MAX_PROG_NAME },
839004d4b27SMathieu Xhonneux };
840004d4b27SMathieu Xhonneux 
841004d4b27SMathieu Xhonneux static int parse_nla_bpf(struct nlattr **attrs, struct seg6_local_lwt *slwt)
842004d4b27SMathieu Xhonneux {
843004d4b27SMathieu Xhonneux 	struct nlattr *tb[SEG6_LOCAL_BPF_PROG_MAX + 1];
844004d4b27SMathieu Xhonneux 	struct bpf_prog *p;
845004d4b27SMathieu Xhonneux 	int ret;
846004d4b27SMathieu Xhonneux 	u32 fd;
847004d4b27SMathieu Xhonneux 
8488cb08174SJohannes Berg 	ret = nla_parse_nested_deprecated(tb, SEG6_LOCAL_BPF_PROG_MAX,
8498cb08174SJohannes Berg 					  attrs[SEG6_LOCAL_BPF],
8508cb08174SJohannes Berg 					  bpf_prog_policy, NULL);
851004d4b27SMathieu Xhonneux 	if (ret < 0)
852004d4b27SMathieu Xhonneux 		return ret;
853004d4b27SMathieu Xhonneux 
854004d4b27SMathieu Xhonneux 	if (!tb[SEG6_LOCAL_BPF_PROG] || !tb[SEG6_LOCAL_BPF_PROG_NAME])
855004d4b27SMathieu Xhonneux 		return -EINVAL;
856004d4b27SMathieu Xhonneux 
857004d4b27SMathieu Xhonneux 	slwt->bpf.name = nla_memdup(tb[SEG6_LOCAL_BPF_PROG_NAME], GFP_KERNEL);
858004d4b27SMathieu Xhonneux 	if (!slwt->bpf.name)
859004d4b27SMathieu Xhonneux 		return -ENOMEM;
860004d4b27SMathieu Xhonneux 
861004d4b27SMathieu Xhonneux 	fd = nla_get_u32(tb[SEG6_LOCAL_BPF_PROG]);
862004d4b27SMathieu Xhonneux 	p = bpf_prog_get_type(fd, BPF_PROG_TYPE_LWT_SEG6LOCAL);
863004d4b27SMathieu Xhonneux 	if (IS_ERR(p)) {
864004d4b27SMathieu Xhonneux 		kfree(slwt->bpf.name);
865004d4b27SMathieu Xhonneux 		return PTR_ERR(p);
866004d4b27SMathieu Xhonneux 	}
867004d4b27SMathieu Xhonneux 
868004d4b27SMathieu Xhonneux 	slwt->bpf.prog = p;
869004d4b27SMathieu Xhonneux 	return 0;
870004d4b27SMathieu Xhonneux }
871004d4b27SMathieu Xhonneux 
872004d4b27SMathieu Xhonneux static int put_nla_bpf(struct sk_buff *skb, struct seg6_local_lwt *slwt)
873004d4b27SMathieu Xhonneux {
874004d4b27SMathieu Xhonneux 	struct nlattr *nest;
875004d4b27SMathieu Xhonneux 
876004d4b27SMathieu Xhonneux 	if (!slwt->bpf.prog)
877004d4b27SMathieu Xhonneux 		return 0;
878004d4b27SMathieu Xhonneux 
879ae0be8deSMichal Kubecek 	nest = nla_nest_start_noflag(skb, SEG6_LOCAL_BPF);
880004d4b27SMathieu Xhonneux 	if (!nest)
881004d4b27SMathieu Xhonneux 		return -EMSGSIZE;
882004d4b27SMathieu Xhonneux 
883004d4b27SMathieu Xhonneux 	if (nla_put_u32(skb, SEG6_LOCAL_BPF_PROG, slwt->bpf.prog->aux->id))
884004d4b27SMathieu Xhonneux 		return -EMSGSIZE;
885004d4b27SMathieu Xhonneux 
886004d4b27SMathieu Xhonneux 	if (slwt->bpf.name &&
887004d4b27SMathieu Xhonneux 	    nla_put_string(skb, SEG6_LOCAL_BPF_PROG_NAME, slwt->bpf.name))
888004d4b27SMathieu Xhonneux 		return -EMSGSIZE;
889004d4b27SMathieu Xhonneux 
890004d4b27SMathieu Xhonneux 	return nla_nest_end(skb, nest);
891004d4b27SMathieu Xhonneux }
892004d4b27SMathieu Xhonneux 
893004d4b27SMathieu Xhonneux static int cmp_nla_bpf(struct seg6_local_lwt *a, struct seg6_local_lwt *b)
894004d4b27SMathieu Xhonneux {
895004d4b27SMathieu Xhonneux 	if (!a->bpf.name && !b->bpf.name)
896004d4b27SMathieu Xhonneux 		return 0;
897004d4b27SMathieu Xhonneux 
898004d4b27SMathieu Xhonneux 	if (!a->bpf.name || !b->bpf.name)
899004d4b27SMathieu Xhonneux 		return 1;
900004d4b27SMathieu Xhonneux 
901004d4b27SMathieu Xhonneux 	return strcmp(a->bpf.name, b->bpf.name);
902004d4b27SMathieu Xhonneux }
903004d4b27SMathieu Xhonneux 
904d1df6fd8SDavid Lebrun struct seg6_action_param {
905d1df6fd8SDavid Lebrun 	int (*parse)(struct nlattr **attrs, struct seg6_local_lwt *slwt);
906d1df6fd8SDavid Lebrun 	int (*put)(struct sk_buff *skb, struct seg6_local_lwt *slwt);
907d1df6fd8SDavid Lebrun 	int (*cmp)(struct seg6_local_lwt *a, struct seg6_local_lwt *b);
908d1df6fd8SDavid Lebrun };
909d1df6fd8SDavid Lebrun 
910d1df6fd8SDavid Lebrun static struct seg6_action_param seg6_action_params[SEG6_LOCAL_MAX + 1] = {
9112d9cc60aSDavid Lebrun 	[SEG6_LOCAL_SRH]	= { .parse = parse_nla_srh,
9122d9cc60aSDavid Lebrun 				    .put = put_nla_srh,
9132d9cc60aSDavid Lebrun 				    .cmp = cmp_nla_srh },
914d1df6fd8SDavid Lebrun 
9152d9cc60aSDavid Lebrun 	[SEG6_LOCAL_TABLE]	= { .parse = parse_nla_table,
9162d9cc60aSDavid Lebrun 				    .put = put_nla_table,
9172d9cc60aSDavid Lebrun 				    .cmp = cmp_nla_table },
918d1df6fd8SDavid Lebrun 
9192d9cc60aSDavid Lebrun 	[SEG6_LOCAL_NH4]	= { .parse = parse_nla_nh4,
9202d9cc60aSDavid Lebrun 				    .put = put_nla_nh4,
9212d9cc60aSDavid Lebrun 				    .cmp = cmp_nla_nh4 },
922d1df6fd8SDavid Lebrun 
9232d9cc60aSDavid Lebrun 	[SEG6_LOCAL_NH6]	= { .parse = parse_nla_nh6,
9242d9cc60aSDavid Lebrun 				    .put = put_nla_nh6,
9252d9cc60aSDavid Lebrun 				    .cmp = cmp_nla_nh6 },
926d1df6fd8SDavid Lebrun 
9272d9cc60aSDavid Lebrun 	[SEG6_LOCAL_IIF]	= { .parse = parse_nla_iif,
9282d9cc60aSDavid Lebrun 				    .put = put_nla_iif,
9292d9cc60aSDavid Lebrun 				    .cmp = cmp_nla_iif },
930d1df6fd8SDavid Lebrun 
9312d9cc60aSDavid Lebrun 	[SEG6_LOCAL_OIF]	= { .parse = parse_nla_oif,
9322d9cc60aSDavid Lebrun 				    .put = put_nla_oif,
9332d9cc60aSDavid Lebrun 				    .cmp = cmp_nla_oif },
934004d4b27SMathieu Xhonneux 
935004d4b27SMathieu Xhonneux 	[SEG6_LOCAL_BPF]	= { .parse = parse_nla_bpf,
936004d4b27SMathieu Xhonneux 				    .put = put_nla_bpf,
937004d4b27SMathieu Xhonneux 				    .cmp = cmp_nla_bpf },
938004d4b27SMathieu Xhonneux 
939d1df6fd8SDavid Lebrun };
940d1df6fd8SDavid Lebrun 
941d1df6fd8SDavid Lebrun static int parse_nla_action(struct nlattr **attrs, struct seg6_local_lwt *slwt)
942d1df6fd8SDavid Lebrun {
943d1df6fd8SDavid Lebrun 	struct seg6_action_param *param;
944d1df6fd8SDavid Lebrun 	struct seg6_action_desc *desc;
945d1df6fd8SDavid Lebrun 	int i, err;
946d1df6fd8SDavid Lebrun 
947d1df6fd8SDavid Lebrun 	desc = __get_action_desc(slwt->action);
948d1df6fd8SDavid Lebrun 	if (!desc)
949d1df6fd8SDavid Lebrun 		return -EINVAL;
950d1df6fd8SDavid Lebrun 
951d1df6fd8SDavid Lebrun 	if (!desc->input)
952d1df6fd8SDavid Lebrun 		return -EOPNOTSUPP;
953d1df6fd8SDavid Lebrun 
954d1df6fd8SDavid Lebrun 	slwt->desc = desc;
955d1df6fd8SDavid Lebrun 	slwt->headroom += desc->static_headroom;
956d1df6fd8SDavid Lebrun 
957d1df6fd8SDavid Lebrun 	for (i = 0; i < SEG6_LOCAL_MAX + 1; i++) {
958d1df6fd8SDavid Lebrun 		if (desc->attrs & (1 << i)) {
959d1df6fd8SDavid Lebrun 			if (!attrs[i])
960d1df6fd8SDavid Lebrun 				return -EINVAL;
961d1df6fd8SDavid Lebrun 
962d1df6fd8SDavid Lebrun 			param = &seg6_action_params[i];
963d1df6fd8SDavid Lebrun 
964d1df6fd8SDavid Lebrun 			err = param->parse(attrs, slwt);
965d1df6fd8SDavid Lebrun 			if (err < 0)
966d1df6fd8SDavid Lebrun 				return err;
967d1df6fd8SDavid Lebrun 		}
968d1df6fd8SDavid Lebrun 	}
969d1df6fd8SDavid Lebrun 
970d1df6fd8SDavid Lebrun 	return 0;
971d1df6fd8SDavid Lebrun }
972d1df6fd8SDavid Lebrun 
973faee6769SAlexander Aring static int seg6_local_build_state(struct net *net, struct nlattr *nla,
974faee6769SAlexander Aring 				  unsigned int family, const void *cfg,
975faee6769SAlexander Aring 				  struct lwtunnel_state **ts,
976d1df6fd8SDavid Lebrun 				  struct netlink_ext_ack *extack)
977d1df6fd8SDavid Lebrun {
978d1df6fd8SDavid Lebrun 	struct nlattr *tb[SEG6_LOCAL_MAX + 1];
979d1df6fd8SDavid Lebrun 	struct lwtunnel_state *newts;
980d1df6fd8SDavid Lebrun 	struct seg6_local_lwt *slwt;
981d1df6fd8SDavid Lebrun 	int err;
982d1df6fd8SDavid Lebrun 
9836285217fSDavid Lebrun 	if (family != AF_INET6)
9846285217fSDavid Lebrun 		return -EINVAL;
9856285217fSDavid Lebrun 
9868cb08174SJohannes Berg 	err = nla_parse_nested_deprecated(tb, SEG6_LOCAL_MAX, nla,
9878cb08174SJohannes Berg 					  seg6_local_policy, extack);
988d1df6fd8SDavid Lebrun 
989d1df6fd8SDavid Lebrun 	if (err < 0)
990d1df6fd8SDavid Lebrun 		return err;
991d1df6fd8SDavid Lebrun 
992d1df6fd8SDavid Lebrun 	if (!tb[SEG6_LOCAL_ACTION])
993d1df6fd8SDavid Lebrun 		return -EINVAL;
994d1df6fd8SDavid Lebrun 
995d1df6fd8SDavid Lebrun 	newts = lwtunnel_state_alloc(sizeof(*slwt));
996d1df6fd8SDavid Lebrun 	if (!newts)
997d1df6fd8SDavid Lebrun 		return -ENOMEM;
998d1df6fd8SDavid Lebrun 
999d1df6fd8SDavid Lebrun 	slwt = seg6_local_lwtunnel(newts);
1000d1df6fd8SDavid Lebrun 	slwt->action = nla_get_u32(tb[SEG6_LOCAL_ACTION]);
1001d1df6fd8SDavid Lebrun 
1002d1df6fd8SDavid Lebrun 	err = parse_nla_action(tb, slwt);
1003d1df6fd8SDavid Lebrun 	if (err < 0)
1004d1df6fd8SDavid Lebrun 		goto out_free;
1005d1df6fd8SDavid Lebrun 
1006d1df6fd8SDavid Lebrun 	newts->type = LWTUNNEL_ENCAP_SEG6_LOCAL;
1007d1df6fd8SDavid Lebrun 	newts->flags = LWTUNNEL_STATE_INPUT_REDIRECT;
1008d1df6fd8SDavid Lebrun 	newts->headroom = slwt->headroom;
1009d1df6fd8SDavid Lebrun 
1010d1df6fd8SDavid Lebrun 	*ts = newts;
1011d1df6fd8SDavid Lebrun 
1012d1df6fd8SDavid Lebrun 	return 0;
1013d1df6fd8SDavid Lebrun 
1014d1df6fd8SDavid Lebrun out_free:
1015d1df6fd8SDavid Lebrun 	kfree(slwt->srh);
1016d1df6fd8SDavid Lebrun 	kfree(newts);
1017d1df6fd8SDavid Lebrun 	return err;
1018d1df6fd8SDavid Lebrun }
1019d1df6fd8SDavid Lebrun 
1020d1df6fd8SDavid Lebrun static void seg6_local_destroy_state(struct lwtunnel_state *lwt)
1021d1df6fd8SDavid Lebrun {
1022d1df6fd8SDavid Lebrun 	struct seg6_local_lwt *slwt = seg6_local_lwtunnel(lwt);
1023d1df6fd8SDavid Lebrun 
1024d1df6fd8SDavid Lebrun 	kfree(slwt->srh);
1025004d4b27SMathieu Xhonneux 
1026004d4b27SMathieu Xhonneux 	if (slwt->desc->attrs & (1 << SEG6_LOCAL_BPF)) {
1027004d4b27SMathieu Xhonneux 		kfree(slwt->bpf.name);
1028004d4b27SMathieu Xhonneux 		bpf_prog_put(slwt->bpf.prog);
1029004d4b27SMathieu Xhonneux 	}
1030004d4b27SMathieu Xhonneux 
1031004d4b27SMathieu Xhonneux 	return;
1032d1df6fd8SDavid Lebrun }
1033d1df6fd8SDavid Lebrun 
1034d1df6fd8SDavid Lebrun static int seg6_local_fill_encap(struct sk_buff *skb,
1035d1df6fd8SDavid Lebrun 				 struct lwtunnel_state *lwt)
1036d1df6fd8SDavid Lebrun {
1037d1df6fd8SDavid Lebrun 	struct seg6_local_lwt *slwt = seg6_local_lwtunnel(lwt);
1038d1df6fd8SDavid Lebrun 	struct seg6_action_param *param;
1039d1df6fd8SDavid Lebrun 	int i, err;
1040d1df6fd8SDavid Lebrun 
1041d1df6fd8SDavid Lebrun 	if (nla_put_u32(skb, SEG6_LOCAL_ACTION, slwt->action))
1042d1df6fd8SDavid Lebrun 		return -EMSGSIZE;
1043d1df6fd8SDavid Lebrun 
1044d1df6fd8SDavid Lebrun 	for (i = 0; i < SEG6_LOCAL_MAX + 1; i++) {
1045d1df6fd8SDavid Lebrun 		if (slwt->desc->attrs & (1 << i)) {
1046d1df6fd8SDavid Lebrun 			param = &seg6_action_params[i];
1047d1df6fd8SDavid Lebrun 			err = param->put(skb, slwt);
1048d1df6fd8SDavid Lebrun 			if (err < 0)
1049d1df6fd8SDavid Lebrun 				return err;
1050d1df6fd8SDavid Lebrun 		}
1051d1df6fd8SDavid Lebrun 	}
1052d1df6fd8SDavid Lebrun 
1053d1df6fd8SDavid Lebrun 	return 0;
1054d1df6fd8SDavid Lebrun }
1055d1df6fd8SDavid Lebrun 
1056d1df6fd8SDavid Lebrun static int seg6_local_get_encap_size(struct lwtunnel_state *lwt)
1057d1df6fd8SDavid Lebrun {
1058d1df6fd8SDavid Lebrun 	struct seg6_local_lwt *slwt = seg6_local_lwtunnel(lwt);
1059d1df6fd8SDavid Lebrun 	unsigned long attrs;
1060d1df6fd8SDavid Lebrun 	int nlsize;
1061d1df6fd8SDavid Lebrun 
1062d1df6fd8SDavid Lebrun 	nlsize = nla_total_size(4); /* action */
1063d1df6fd8SDavid Lebrun 
1064d1df6fd8SDavid Lebrun 	attrs = slwt->desc->attrs;
1065d1df6fd8SDavid Lebrun 
1066d1df6fd8SDavid Lebrun 	if (attrs & (1 << SEG6_LOCAL_SRH))
1067d1df6fd8SDavid Lebrun 		nlsize += nla_total_size((slwt->srh->hdrlen + 1) << 3);
1068d1df6fd8SDavid Lebrun 
1069d1df6fd8SDavid Lebrun 	if (attrs & (1 << SEG6_LOCAL_TABLE))
1070d1df6fd8SDavid Lebrun 		nlsize += nla_total_size(4);
1071d1df6fd8SDavid Lebrun 
1072d1df6fd8SDavid Lebrun 	if (attrs & (1 << SEG6_LOCAL_NH4))
1073d1df6fd8SDavid Lebrun 		nlsize += nla_total_size(4);
1074d1df6fd8SDavid Lebrun 
1075d1df6fd8SDavid Lebrun 	if (attrs & (1 << SEG6_LOCAL_NH6))
1076d1df6fd8SDavid Lebrun 		nlsize += nla_total_size(16);
1077d1df6fd8SDavid Lebrun 
1078d1df6fd8SDavid Lebrun 	if (attrs & (1 << SEG6_LOCAL_IIF))
1079d1df6fd8SDavid Lebrun 		nlsize += nla_total_size(4);
1080d1df6fd8SDavid Lebrun 
1081d1df6fd8SDavid Lebrun 	if (attrs & (1 << SEG6_LOCAL_OIF))
1082d1df6fd8SDavid Lebrun 		nlsize += nla_total_size(4);
1083d1df6fd8SDavid Lebrun 
1084004d4b27SMathieu Xhonneux 	if (attrs & (1 << SEG6_LOCAL_BPF))
1085004d4b27SMathieu Xhonneux 		nlsize += nla_total_size(sizeof(struct nlattr)) +
1086004d4b27SMathieu Xhonneux 		       nla_total_size(MAX_PROG_NAME) +
1087004d4b27SMathieu Xhonneux 		       nla_total_size(4);
1088004d4b27SMathieu Xhonneux 
1089d1df6fd8SDavid Lebrun 	return nlsize;
1090d1df6fd8SDavid Lebrun }
1091d1df6fd8SDavid Lebrun 
1092d1df6fd8SDavid Lebrun static int seg6_local_cmp_encap(struct lwtunnel_state *a,
1093d1df6fd8SDavid Lebrun 				struct lwtunnel_state *b)
1094d1df6fd8SDavid Lebrun {
1095d1df6fd8SDavid Lebrun 	struct seg6_local_lwt *slwt_a, *slwt_b;
1096d1df6fd8SDavid Lebrun 	struct seg6_action_param *param;
1097d1df6fd8SDavid Lebrun 	int i;
1098d1df6fd8SDavid Lebrun 
1099d1df6fd8SDavid Lebrun 	slwt_a = seg6_local_lwtunnel(a);
1100d1df6fd8SDavid Lebrun 	slwt_b = seg6_local_lwtunnel(b);
1101d1df6fd8SDavid Lebrun 
1102d1df6fd8SDavid Lebrun 	if (slwt_a->action != slwt_b->action)
1103d1df6fd8SDavid Lebrun 		return 1;
1104d1df6fd8SDavid Lebrun 
1105d1df6fd8SDavid Lebrun 	if (slwt_a->desc->attrs != slwt_b->desc->attrs)
1106d1df6fd8SDavid Lebrun 		return 1;
1107d1df6fd8SDavid Lebrun 
1108d1df6fd8SDavid Lebrun 	for (i = 0; i < SEG6_LOCAL_MAX + 1; i++) {
1109d1df6fd8SDavid Lebrun 		if (slwt_a->desc->attrs & (1 << i)) {
1110d1df6fd8SDavid Lebrun 			param = &seg6_action_params[i];
1111d1df6fd8SDavid Lebrun 			if (param->cmp(slwt_a, slwt_b))
1112d1df6fd8SDavid Lebrun 				return 1;
1113d1df6fd8SDavid Lebrun 		}
1114d1df6fd8SDavid Lebrun 	}
1115d1df6fd8SDavid Lebrun 
1116d1df6fd8SDavid Lebrun 	return 0;
1117d1df6fd8SDavid Lebrun }
1118d1df6fd8SDavid Lebrun 
1119d1df6fd8SDavid Lebrun static const struct lwtunnel_encap_ops seg6_local_ops = {
1120d1df6fd8SDavid Lebrun 	.build_state	= seg6_local_build_state,
1121d1df6fd8SDavid Lebrun 	.destroy_state	= seg6_local_destroy_state,
1122d1df6fd8SDavid Lebrun 	.input		= seg6_local_input,
1123d1df6fd8SDavid Lebrun 	.fill_encap	= seg6_local_fill_encap,
1124d1df6fd8SDavid Lebrun 	.get_encap_size	= seg6_local_get_encap_size,
1125d1df6fd8SDavid Lebrun 	.cmp_encap	= seg6_local_cmp_encap,
1126d1df6fd8SDavid Lebrun 	.owner		= THIS_MODULE,
1127d1df6fd8SDavid Lebrun };
1128d1df6fd8SDavid Lebrun 
1129d1df6fd8SDavid Lebrun int __init seg6_local_init(void)
1130d1df6fd8SDavid Lebrun {
1131d1df6fd8SDavid Lebrun 	return lwtunnel_encap_add_ops(&seg6_local_ops,
1132d1df6fd8SDavid Lebrun 				      LWTUNNEL_ENCAP_SEG6_LOCAL);
1133d1df6fd8SDavid Lebrun }
1134d1df6fd8SDavid Lebrun 
1135d1df6fd8SDavid Lebrun void seg6_local_exit(void)
1136d1df6fd8SDavid Lebrun {
1137d1df6fd8SDavid Lebrun 	lwtunnel_encap_del_ops(&seg6_local_ops, LWTUNNEL_ENCAP_SEG6_LOCAL);
1138d1df6fd8SDavid Lebrun }
1139