xref: /openbmc/linux/net/ipv6/seg6_local.c (revision 0a3021f1d4e553d9f3e7fc20e994b91af0687eb4)
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;
39*0a3021f1SAndrea Mayer 
40*0a3021f1SAndrea Mayer 	/* The optattrs field is used for specifying all the optional
41*0a3021f1SAndrea Mayer 	 * attributes supported by a specific behavior.
42*0a3021f1SAndrea Mayer 	 * It means that if one of these attributes is not provided in the
43*0a3021f1SAndrea Mayer 	 * netlink message during the behavior creation, no errors will be
44*0a3021f1SAndrea Mayer 	 * returned to the userspace.
45*0a3021f1SAndrea Mayer 	 *
46*0a3021f1SAndrea Mayer 	 * Each attribute can be only of two types (mutually exclusive):
47*0a3021f1SAndrea Mayer 	 * 1) required or 2) optional.
48*0a3021f1SAndrea Mayer 	 * Every user MUST obey to this rule! If you set an attribute as
49*0a3021f1SAndrea Mayer 	 * required the same attribute CANNOT be set as optional and vice
50*0a3021f1SAndrea Mayer 	 * versa.
51*0a3021f1SAndrea Mayer 	 */
52*0a3021f1SAndrea Mayer 	unsigned long optattrs;
53*0a3021f1SAndrea Mayer 
54d1df6fd8SDavid Lebrun 	int (*input)(struct sk_buff *skb, struct seg6_local_lwt *slwt);
55d1df6fd8SDavid Lebrun 	int static_headroom;
56d1df6fd8SDavid Lebrun };
57d1df6fd8SDavid Lebrun 
58004d4b27SMathieu Xhonneux struct bpf_lwt_prog {
59004d4b27SMathieu Xhonneux 	struct bpf_prog *prog;
60004d4b27SMathieu Xhonneux 	char *name;
61004d4b27SMathieu Xhonneux };
62004d4b27SMathieu Xhonneux 
63d1df6fd8SDavid Lebrun struct seg6_local_lwt {
64d1df6fd8SDavid Lebrun 	int action;
65d1df6fd8SDavid Lebrun 	struct ipv6_sr_hdr *srh;
66d1df6fd8SDavid Lebrun 	int table;
67d1df6fd8SDavid Lebrun 	struct in_addr nh4;
68d1df6fd8SDavid Lebrun 	struct in6_addr nh6;
69d1df6fd8SDavid Lebrun 	int iif;
70d1df6fd8SDavid Lebrun 	int oif;
71004d4b27SMathieu Xhonneux 	struct bpf_lwt_prog bpf;
72d1df6fd8SDavid Lebrun 
73d1df6fd8SDavid Lebrun 	int headroom;
74d1df6fd8SDavid Lebrun 	struct seg6_action_desc *desc;
75*0a3021f1SAndrea Mayer 	/* unlike the required attrs, we have to track the optional attributes
76*0a3021f1SAndrea Mayer 	 * that have been effectively parsed.
77*0a3021f1SAndrea Mayer 	 */
78*0a3021f1SAndrea Mayer 	unsigned long parsed_optattrs;
79d1df6fd8SDavid Lebrun };
80d1df6fd8SDavid Lebrun 
81d1df6fd8SDavid Lebrun static struct seg6_local_lwt *seg6_local_lwtunnel(struct lwtunnel_state *lwt)
82d1df6fd8SDavid Lebrun {
83d1df6fd8SDavid Lebrun 	return (struct seg6_local_lwt *)lwt->data;
84d1df6fd8SDavid Lebrun }
85d1df6fd8SDavid Lebrun 
86140f04c3SDavid Lebrun static struct ipv6_sr_hdr *get_srh(struct sk_buff *skb)
87140f04c3SDavid Lebrun {
88140f04c3SDavid Lebrun 	struct ipv6_sr_hdr *srh;
895829d70bSAhmed Abdelsalam 	int len, srhoff = 0;
90140f04c3SDavid Lebrun 
915829d70bSAhmed Abdelsalam 	if (ipv6_find_hdr(skb, &srhoff, IPPROTO_ROUTING, NULL, NULL) < 0)
92140f04c3SDavid Lebrun 		return NULL;
93140f04c3SDavid Lebrun 
945829d70bSAhmed Abdelsalam 	if (!pskb_may_pull(skb, srhoff + sizeof(*srh)))
955829d70bSAhmed Abdelsalam 		return NULL;
965829d70bSAhmed Abdelsalam 
975829d70bSAhmed Abdelsalam 	srh = (struct ipv6_sr_hdr *)(skb->data + srhoff);
985829d70bSAhmed Abdelsalam 
99140f04c3SDavid Lebrun 	len = (srh->hdrlen + 1) << 3;
100140f04c3SDavid Lebrun 
1015829d70bSAhmed Abdelsalam 	if (!pskb_may_pull(skb, srhoff + len))
102140f04c3SDavid Lebrun 		return NULL;
103140f04c3SDavid Lebrun 
1047f91ed8cSAndrea Mayer 	/* note that pskb_may_pull may change pointers in header;
1057f91ed8cSAndrea Mayer 	 * for this reason it is necessary to reload them when needed.
1067f91ed8cSAndrea Mayer 	 */
1077f91ed8cSAndrea Mayer 	srh = (struct ipv6_sr_hdr *)(skb->data + srhoff);
1087f91ed8cSAndrea Mayer 
109bb986a50SAhmed Abdelsalam 	if (!seg6_validate_srh(srh, len, true))
110140f04c3SDavid Lebrun 		return NULL;
111140f04c3SDavid Lebrun 
112140f04c3SDavid Lebrun 	return srh;
113140f04c3SDavid Lebrun }
114140f04c3SDavid Lebrun 
115140f04c3SDavid Lebrun static struct ipv6_sr_hdr *get_and_validate_srh(struct sk_buff *skb)
116140f04c3SDavid Lebrun {
117140f04c3SDavid Lebrun 	struct ipv6_sr_hdr *srh;
118140f04c3SDavid Lebrun 
119140f04c3SDavid Lebrun 	srh = get_srh(skb);
120140f04c3SDavid Lebrun 	if (!srh)
121140f04c3SDavid Lebrun 		return NULL;
122140f04c3SDavid Lebrun 
123140f04c3SDavid Lebrun 	if (srh->segments_left == 0)
124140f04c3SDavid Lebrun 		return NULL;
125140f04c3SDavid Lebrun 
126140f04c3SDavid Lebrun #ifdef CONFIG_IPV6_SEG6_HMAC
127140f04c3SDavid Lebrun 	if (!seg6_hmac_validate_skb(skb))
128140f04c3SDavid Lebrun 		return NULL;
129140f04c3SDavid Lebrun #endif
130140f04c3SDavid Lebrun 
131140f04c3SDavid Lebrun 	return srh;
132140f04c3SDavid Lebrun }
133140f04c3SDavid Lebrun 
134d7a669ddSDavid Lebrun static bool decap_and_validate(struct sk_buff *skb, int proto)
135d7a669ddSDavid Lebrun {
136d7a669ddSDavid Lebrun 	struct ipv6_sr_hdr *srh;
137d7a669ddSDavid Lebrun 	unsigned int off = 0;
138d7a669ddSDavid Lebrun 
139d7a669ddSDavid Lebrun 	srh = get_srh(skb);
140d7a669ddSDavid Lebrun 	if (srh && srh->segments_left > 0)
141d7a669ddSDavid Lebrun 		return false;
142d7a669ddSDavid Lebrun 
143d7a669ddSDavid Lebrun #ifdef CONFIG_IPV6_SEG6_HMAC
144d7a669ddSDavid Lebrun 	if (srh && !seg6_hmac_validate_skb(skb))
145d7a669ddSDavid Lebrun 		return false;
146d7a669ddSDavid Lebrun #endif
147d7a669ddSDavid Lebrun 
148d7a669ddSDavid Lebrun 	if (ipv6_find_hdr(skb, &off, proto, NULL, NULL) < 0)
149d7a669ddSDavid Lebrun 		return false;
150d7a669ddSDavid Lebrun 
151d7a669ddSDavid Lebrun 	if (!pskb_pull(skb, off))
152d7a669ddSDavid Lebrun 		return false;
153d7a669ddSDavid Lebrun 
154d7a669ddSDavid Lebrun 	skb_postpull_rcsum(skb, skb_network_header(skb), off);
155d7a669ddSDavid Lebrun 
156d7a669ddSDavid Lebrun 	skb_reset_network_header(skb);
157d7a669ddSDavid Lebrun 	skb_reset_transport_header(skb);
15862ebaeaeSYuki Taguchi 	if (iptunnel_pull_offloads(skb))
15962ebaeaeSYuki Taguchi 		return false;
160d7a669ddSDavid Lebrun 
161d7a669ddSDavid Lebrun 	return true;
162d7a669ddSDavid Lebrun }
163d7a669ddSDavid Lebrun 
164d7a669ddSDavid Lebrun static void advance_nextseg(struct ipv6_sr_hdr *srh, struct in6_addr *daddr)
165d7a669ddSDavid Lebrun {
166d7a669ddSDavid Lebrun 	struct in6_addr *addr;
167d7a669ddSDavid Lebrun 
168d7a669ddSDavid Lebrun 	srh->segments_left--;
169d7a669ddSDavid Lebrun 	addr = srh->segments + srh->segments_left;
170d7a669ddSDavid Lebrun 	*daddr = *addr;
171d7a669ddSDavid Lebrun }
172d7a669ddSDavid Lebrun 
173fd1fef0cSAndrea Mayer static int
174fd1fef0cSAndrea Mayer seg6_lookup_any_nexthop(struct sk_buff *skb, struct in6_addr *nhaddr,
175fd1fef0cSAndrea Mayer 			u32 tbl_id, bool local_delivery)
176d7a669ddSDavid Lebrun {
177d7a669ddSDavid Lebrun 	struct net *net = dev_net(skb->dev);
178d7a669ddSDavid Lebrun 	struct ipv6hdr *hdr = ipv6_hdr(skb);
179d7a669ddSDavid Lebrun 	int flags = RT6_LOOKUP_F_HAS_SADDR;
180d7a669ddSDavid Lebrun 	struct dst_entry *dst = NULL;
181d7a669ddSDavid Lebrun 	struct rt6_info *rt;
182d7a669ddSDavid Lebrun 	struct flowi6 fl6;
183fd1fef0cSAndrea Mayer 	int dev_flags = 0;
184d7a669ddSDavid Lebrun 
185d7a669ddSDavid Lebrun 	fl6.flowi6_iif = skb->dev->ifindex;
186d7a669ddSDavid Lebrun 	fl6.daddr = nhaddr ? *nhaddr : hdr->daddr;
187d7a669ddSDavid Lebrun 	fl6.saddr = hdr->saddr;
188d7a669ddSDavid Lebrun 	fl6.flowlabel = ip6_flowinfo(hdr);
189d7a669ddSDavid Lebrun 	fl6.flowi6_mark = skb->mark;
190d7a669ddSDavid Lebrun 	fl6.flowi6_proto = hdr->nexthdr;
191d7a669ddSDavid Lebrun 
192d7a669ddSDavid Lebrun 	if (nhaddr)
193d7a669ddSDavid Lebrun 		fl6.flowi6_flags = FLOWI_FLAG_KNOWN_NH;
194d7a669ddSDavid Lebrun 
195d7a669ddSDavid Lebrun 	if (!tbl_id) {
196b75cc8f9SDavid Ahern 		dst = ip6_route_input_lookup(net, skb->dev, &fl6, skb, flags);
197d7a669ddSDavid Lebrun 	} else {
198d7a669ddSDavid Lebrun 		struct fib6_table *table;
199d7a669ddSDavid Lebrun 
200d7a669ddSDavid Lebrun 		table = fib6_get_table(net, tbl_id);
201d7a669ddSDavid Lebrun 		if (!table)
202d7a669ddSDavid Lebrun 			goto out;
203d7a669ddSDavid Lebrun 
204b75cc8f9SDavid Ahern 		rt = ip6_pol_route(net, table, 0, &fl6, skb, flags);
205d7a669ddSDavid Lebrun 		dst = &rt->dst;
206d7a669ddSDavid Lebrun 	}
207d7a669ddSDavid Lebrun 
208fd1fef0cSAndrea Mayer 	/* we want to discard traffic destined for local packet processing,
209fd1fef0cSAndrea Mayer 	 * if @local_delivery is set to false.
210fd1fef0cSAndrea Mayer 	 */
211fd1fef0cSAndrea Mayer 	if (!local_delivery)
212fd1fef0cSAndrea Mayer 		dev_flags |= IFF_LOOPBACK;
213fd1fef0cSAndrea Mayer 
214fd1fef0cSAndrea Mayer 	if (dst && (dst->dev->flags & dev_flags) && !dst->error) {
215d7a669ddSDavid Lebrun 		dst_release(dst);
216d7a669ddSDavid Lebrun 		dst = NULL;
217d7a669ddSDavid Lebrun 	}
218d7a669ddSDavid Lebrun 
219d7a669ddSDavid Lebrun out:
220d7a669ddSDavid Lebrun 	if (!dst) {
221d7a669ddSDavid Lebrun 		rt = net->ipv6.ip6_blk_hole_entry;
222d7a669ddSDavid Lebrun 		dst = &rt->dst;
223d7a669ddSDavid Lebrun 		dst_hold(dst);
224d7a669ddSDavid Lebrun 	}
225d7a669ddSDavid Lebrun 
226d7a669ddSDavid Lebrun 	skb_dst_drop(skb);
227d7a669ddSDavid Lebrun 	skb_dst_set(skb, dst);
2281c1e761eSMathieu Xhonneux 	return dst->error;
229d7a669ddSDavid Lebrun }
230d7a669ddSDavid Lebrun 
231fd1fef0cSAndrea Mayer int seg6_lookup_nexthop(struct sk_buff *skb,
232fd1fef0cSAndrea Mayer 			struct in6_addr *nhaddr, u32 tbl_id)
233fd1fef0cSAndrea Mayer {
234fd1fef0cSAndrea Mayer 	return seg6_lookup_any_nexthop(skb, nhaddr, tbl_id, false);
235fd1fef0cSAndrea Mayer }
236fd1fef0cSAndrea Mayer 
237140f04c3SDavid Lebrun /* regular endpoint function */
238140f04c3SDavid Lebrun static int input_action_end(struct sk_buff *skb, struct seg6_local_lwt *slwt)
239140f04c3SDavid Lebrun {
240140f04c3SDavid Lebrun 	struct ipv6_sr_hdr *srh;
241140f04c3SDavid Lebrun 
242140f04c3SDavid Lebrun 	srh = get_and_validate_srh(skb);
243140f04c3SDavid Lebrun 	if (!srh)
244140f04c3SDavid Lebrun 		goto drop;
245140f04c3SDavid Lebrun 
246d7a669ddSDavid Lebrun 	advance_nextseg(srh, &ipv6_hdr(skb)->daddr);
247140f04c3SDavid Lebrun 
2481c1e761eSMathieu Xhonneux 	seg6_lookup_nexthop(skb, NULL, 0);
249140f04c3SDavid Lebrun 
250140f04c3SDavid Lebrun 	return dst_input(skb);
251140f04c3SDavid Lebrun 
252140f04c3SDavid Lebrun drop:
253140f04c3SDavid Lebrun 	kfree_skb(skb);
254140f04c3SDavid Lebrun 	return -EINVAL;
255140f04c3SDavid Lebrun }
256140f04c3SDavid Lebrun 
257140f04c3SDavid Lebrun /* regular endpoint, and forward to specified nexthop */
258140f04c3SDavid Lebrun static int input_action_end_x(struct sk_buff *skb, struct seg6_local_lwt *slwt)
259140f04c3SDavid Lebrun {
260140f04c3SDavid Lebrun 	struct ipv6_sr_hdr *srh;
261140f04c3SDavid Lebrun 
262140f04c3SDavid Lebrun 	srh = get_and_validate_srh(skb);
263140f04c3SDavid Lebrun 	if (!srh)
264140f04c3SDavid Lebrun 		goto drop;
265140f04c3SDavid Lebrun 
266d7a669ddSDavid Lebrun 	advance_nextseg(srh, &ipv6_hdr(skb)->daddr);
267140f04c3SDavid Lebrun 
2681c1e761eSMathieu Xhonneux 	seg6_lookup_nexthop(skb, &slwt->nh6, 0);
269140f04c3SDavid Lebrun 
270140f04c3SDavid Lebrun 	return dst_input(skb);
271140f04c3SDavid Lebrun 
272140f04c3SDavid Lebrun drop:
273140f04c3SDavid Lebrun 	kfree_skb(skb);
274140f04c3SDavid Lebrun 	return -EINVAL;
275140f04c3SDavid Lebrun }
276140f04c3SDavid Lebrun 
277891ef8ddSDavid Lebrun static int input_action_end_t(struct sk_buff *skb, struct seg6_local_lwt *slwt)
278891ef8ddSDavid Lebrun {
279891ef8ddSDavid Lebrun 	struct ipv6_sr_hdr *srh;
280891ef8ddSDavid Lebrun 
281891ef8ddSDavid Lebrun 	srh = get_and_validate_srh(skb);
282891ef8ddSDavid Lebrun 	if (!srh)
283891ef8ddSDavid Lebrun 		goto drop;
284891ef8ddSDavid Lebrun 
285891ef8ddSDavid Lebrun 	advance_nextseg(srh, &ipv6_hdr(skb)->daddr);
286891ef8ddSDavid Lebrun 
2871c1e761eSMathieu Xhonneux 	seg6_lookup_nexthop(skb, NULL, slwt->table);
288891ef8ddSDavid Lebrun 
289891ef8ddSDavid Lebrun 	return dst_input(skb);
290891ef8ddSDavid Lebrun 
291891ef8ddSDavid Lebrun drop:
292891ef8ddSDavid Lebrun 	kfree_skb(skb);
293891ef8ddSDavid Lebrun 	return -EINVAL;
294891ef8ddSDavid Lebrun }
295891ef8ddSDavid Lebrun 
296891ef8ddSDavid Lebrun /* decapsulate and forward inner L2 frame on specified interface */
297891ef8ddSDavid Lebrun static int input_action_end_dx2(struct sk_buff *skb,
298891ef8ddSDavid Lebrun 				struct seg6_local_lwt *slwt)
299891ef8ddSDavid Lebrun {
300891ef8ddSDavid Lebrun 	struct net *net = dev_net(skb->dev);
301891ef8ddSDavid Lebrun 	struct net_device *odev;
302891ef8ddSDavid Lebrun 	struct ethhdr *eth;
303891ef8ddSDavid Lebrun 
30426776253SPaolo Lungaroni 	if (!decap_and_validate(skb, IPPROTO_ETHERNET))
305891ef8ddSDavid Lebrun 		goto drop;
306891ef8ddSDavid Lebrun 
307891ef8ddSDavid Lebrun 	if (!pskb_may_pull(skb, ETH_HLEN))
308891ef8ddSDavid Lebrun 		goto drop;
309891ef8ddSDavid Lebrun 
310891ef8ddSDavid Lebrun 	skb_reset_mac_header(skb);
311891ef8ddSDavid Lebrun 	eth = (struct ethhdr *)skb->data;
312891ef8ddSDavid Lebrun 
313891ef8ddSDavid Lebrun 	/* To determine the frame's protocol, we assume it is 802.3. This avoids
314891ef8ddSDavid Lebrun 	 * a call to eth_type_trans(), which is not really relevant for our
315891ef8ddSDavid Lebrun 	 * use case.
316891ef8ddSDavid Lebrun 	 */
317891ef8ddSDavid Lebrun 	if (!eth_proto_is_802_3(eth->h_proto))
318891ef8ddSDavid Lebrun 		goto drop;
319891ef8ddSDavid Lebrun 
320891ef8ddSDavid Lebrun 	odev = dev_get_by_index_rcu(net, slwt->oif);
321891ef8ddSDavid Lebrun 	if (!odev)
322891ef8ddSDavid Lebrun 		goto drop;
323891ef8ddSDavid Lebrun 
324891ef8ddSDavid Lebrun 	/* As we accept Ethernet frames, make sure the egress device is of
325891ef8ddSDavid Lebrun 	 * the correct type.
326891ef8ddSDavid Lebrun 	 */
327891ef8ddSDavid Lebrun 	if (odev->type != ARPHRD_ETHER)
328891ef8ddSDavid Lebrun 		goto drop;
329891ef8ddSDavid Lebrun 
330891ef8ddSDavid Lebrun 	if (!(odev->flags & IFF_UP) || !netif_carrier_ok(odev))
331891ef8ddSDavid Lebrun 		goto drop;
332891ef8ddSDavid Lebrun 
333891ef8ddSDavid Lebrun 	skb_orphan(skb);
334891ef8ddSDavid Lebrun 
335891ef8ddSDavid Lebrun 	if (skb_warn_if_lro(skb))
336891ef8ddSDavid Lebrun 		goto drop;
337891ef8ddSDavid Lebrun 
338891ef8ddSDavid Lebrun 	skb_forward_csum(skb);
339891ef8ddSDavid Lebrun 
340891ef8ddSDavid Lebrun 	if (skb->len - ETH_HLEN > odev->mtu)
341891ef8ddSDavid Lebrun 		goto drop;
342891ef8ddSDavid Lebrun 
343891ef8ddSDavid Lebrun 	skb->dev = odev;
344891ef8ddSDavid Lebrun 	skb->protocol = eth->h_proto;
345891ef8ddSDavid Lebrun 
346891ef8ddSDavid Lebrun 	return dev_queue_xmit(skb);
347891ef8ddSDavid Lebrun 
348891ef8ddSDavid Lebrun drop:
349891ef8ddSDavid Lebrun 	kfree_skb(skb);
350891ef8ddSDavid Lebrun 	return -EINVAL;
351891ef8ddSDavid Lebrun }
352891ef8ddSDavid Lebrun 
353140f04c3SDavid Lebrun /* decapsulate and forward to specified nexthop */
354140f04c3SDavid Lebrun static int input_action_end_dx6(struct sk_buff *skb,
355140f04c3SDavid Lebrun 				struct seg6_local_lwt *slwt)
356140f04c3SDavid Lebrun {
357d7a669ddSDavid Lebrun 	struct in6_addr *nhaddr = NULL;
358140f04c3SDavid Lebrun 
359140f04c3SDavid Lebrun 	/* this function accepts IPv6 encapsulated packets, with either
360140f04c3SDavid Lebrun 	 * an SRH with SL=0, or no SRH.
361140f04c3SDavid Lebrun 	 */
362140f04c3SDavid Lebrun 
363d7a669ddSDavid Lebrun 	if (!decap_and_validate(skb, IPPROTO_IPV6))
364140f04c3SDavid Lebrun 		goto drop;
365140f04c3SDavid Lebrun 
366d7a669ddSDavid Lebrun 	if (!pskb_may_pull(skb, sizeof(struct ipv6hdr)))
367140f04c3SDavid Lebrun 		goto drop;
368140f04c3SDavid Lebrun 
369140f04c3SDavid Lebrun 	/* The inner packet is not associated to any local interface,
370140f04c3SDavid Lebrun 	 * so we do not call netif_rx().
371140f04c3SDavid Lebrun 	 *
372140f04c3SDavid Lebrun 	 * If slwt->nh6 is set to ::, then lookup the nexthop for the
373140f04c3SDavid Lebrun 	 * inner packet's DA. Otherwise, use the specified nexthop.
374140f04c3SDavid Lebrun 	 */
375140f04c3SDavid Lebrun 
376d7a669ddSDavid Lebrun 	if (!ipv6_addr_any(&slwt->nh6))
377d7a669ddSDavid Lebrun 		nhaddr = &slwt->nh6;
378140f04c3SDavid Lebrun 
379c71644d0SAndrea Mayer 	skb_set_transport_header(skb, sizeof(struct ipv6hdr));
380c71644d0SAndrea Mayer 
3811c1e761eSMathieu Xhonneux 	seg6_lookup_nexthop(skb, nhaddr, 0);
382140f04c3SDavid Lebrun 
383140f04c3SDavid Lebrun 	return dst_input(skb);
384140f04c3SDavid Lebrun drop:
385140f04c3SDavid Lebrun 	kfree_skb(skb);
386140f04c3SDavid Lebrun 	return -EINVAL;
387140f04c3SDavid Lebrun }
388140f04c3SDavid Lebrun 
389891ef8ddSDavid Lebrun static int input_action_end_dx4(struct sk_buff *skb,
390891ef8ddSDavid Lebrun 				struct seg6_local_lwt *slwt)
391891ef8ddSDavid Lebrun {
392891ef8ddSDavid Lebrun 	struct iphdr *iph;
393891ef8ddSDavid Lebrun 	__be32 nhaddr;
394891ef8ddSDavid Lebrun 	int err;
395891ef8ddSDavid Lebrun 
396891ef8ddSDavid Lebrun 	if (!decap_and_validate(skb, IPPROTO_IPIP))
397891ef8ddSDavid Lebrun 		goto drop;
398891ef8ddSDavid Lebrun 
399891ef8ddSDavid Lebrun 	if (!pskb_may_pull(skb, sizeof(struct iphdr)))
400891ef8ddSDavid Lebrun 		goto drop;
401891ef8ddSDavid Lebrun 
402891ef8ddSDavid Lebrun 	skb->protocol = htons(ETH_P_IP);
403891ef8ddSDavid Lebrun 
404891ef8ddSDavid Lebrun 	iph = ip_hdr(skb);
405891ef8ddSDavid Lebrun 
406891ef8ddSDavid Lebrun 	nhaddr = slwt->nh4.s_addr ?: iph->daddr;
407891ef8ddSDavid Lebrun 
408891ef8ddSDavid Lebrun 	skb_dst_drop(skb);
409891ef8ddSDavid Lebrun 
410c71644d0SAndrea Mayer 	skb_set_transport_header(skb, sizeof(struct iphdr));
411c71644d0SAndrea Mayer 
412891ef8ddSDavid Lebrun 	err = ip_route_input(skb, nhaddr, iph->saddr, 0, skb->dev);
413891ef8ddSDavid Lebrun 	if (err)
414891ef8ddSDavid Lebrun 		goto drop;
415891ef8ddSDavid Lebrun 
416891ef8ddSDavid Lebrun 	return dst_input(skb);
417891ef8ddSDavid Lebrun 
418891ef8ddSDavid Lebrun drop:
419891ef8ddSDavid Lebrun 	kfree_skb(skb);
420891ef8ddSDavid Lebrun 	return -EINVAL;
421891ef8ddSDavid Lebrun }
422891ef8ddSDavid Lebrun 
423891ef8ddSDavid Lebrun static int input_action_end_dt6(struct sk_buff *skb,
424891ef8ddSDavid Lebrun 				struct seg6_local_lwt *slwt)
425891ef8ddSDavid Lebrun {
426891ef8ddSDavid Lebrun 	if (!decap_and_validate(skb, IPPROTO_IPV6))
427891ef8ddSDavid Lebrun 		goto drop;
428891ef8ddSDavid Lebrun 
429891ef8ddSDavid Lebrun 	if (!pskb_may_pull(skb, sizeof(struct ipv6hdr)))
430891ef8ddSDavid Lebrun 		goto drop;
431891ef8ddSDavid Lebrun 
432c71644d0SAndrea Mayer 	skb_set_transport_header(skb, sizeof(struct ipv6hdr));
433c71644d0SAndrea Mayer 
434fd1fef0cSAndrea Mayer 	seg6_lookup_any_nexthop(skb, NULL, slwt->table, true);
435891ef8ddSDavid Lebrun 
436891ef8ddSDavid Lebrun 	return dst_input(skb);
437891ef8ddSDavid Lebrun 
438891ef8ddSDavid Lebrun drop:
439891ef8ddSDavid Lebrun 	kfree_skb(skb);
440891ef8ddSDavid Lebrun 	return -EINVAL;
441891ef8ddSDavid Lebrun }
442891ef8ddSDavid Lebrun 
443140f04c3SDavid Lebrun /* push an SRH on top of the current one */
444140f04c3SDavid Lebrun static int input_action_end_b6(struct sk_buff *skb, struct seg6_local_lwt *slwt)
445140f04c3SDavid Lebrun {
446140f04c3SDavid Lebrun 	struct ipv6_sr_hdr *srh;
447140f04c3SDavid Lebrun 	int err = -EINVAL;
448140f04c3SDavid Lebrun 
449140f04c3SDavid Lebrun 	srh = get_and_validate_srh(skb);
450140f04c3SDavid Lebrun 	if (!srh)
451140f04c3SDavid Lebrun 		goto drop;
452140f04c3SDavid Lebrun 
453140f04c3SDavid Lebrun 	err = seg6_do_srh_inline(skb, slwt->srh);
454140f04c3SDavid Lebrun 	if (err)
455140f04c3SDavid Lebrun 		goto drop;
456140f04c3SDavid Lebrun 
457140f04c3SDavid Lebrun 	ipv6_hdr(skb)->payload_len = htons(skb->len - sizeof(struct ipv6hdr));
458140f04c3SDavid Lebrun 	skb_set_transport_header(skb, sizeof(struct ipv6hdr));
459140f04c3SDavid Lebrun 
4601c1e761eSMathieu Xhonneux 	seg6_lookup_nexthop(skb, NULL, 0);
461140f04c3SDavid Lebrun 
462140f04c3SDavid Lebrun 	return dst_input(skb);
463140f04c3SDavid Lebrun 
464140f04c3SDavid Lebrun drop:
465140f04c3SDavid Lebrun 	kfree_skb(skb);
466140f04c3SDavid Lebrun 	return err;
467140f04c3SDavid Lebrun }
468140f04c3SDavid Lebrun 
469140f04c3SDavid Lebrun /* encapsulate within an outer IPv6 header and a specified SRH */
470140f04c3SDavid Lebrun static int input_action_end_b6_encap(struct sk_buff *skb,
471140f04c3SDavid Lebrun 				     struct seg6_local_lwt *slwt)
472140f04c3SDavid Lebrun {
473140f04c3SDavid Lebrun 	struct ipv6_sr_hdr *srh;
474140f04c3SDavid Lebrun 	int err = -EINVAL;
475140f04c3SDavid Lebrun 
476140f04c3SDavid Lebrun 	srh = get_and_validate_srh(skb);
477140f04c3SDavid Lebrun 	if (!srh)
478140f04c3SDavid Lebrun 		goto drop;
479140f04c3SDavid Lebrun 
480d7a669ddSDavid Lebrun 	advance_nextseg(srh, &ipv6_hdr(skb)->daddr);
481140f04c3SDavid Lebrun 
482140f04c3SDavid Lebrun 	skb_reset_inner_headers(skb);
483140f04c3SDavid Lebrun 	skb->encapsulation = 1;
484140f04c3SDavid Lebrun 
48532d99d0bSDavid Lebrun 	err = seg6_do_srh_encap(skb, slwt->srh, IPPROTO_IPV6);
486140f04c3SDavid Lebrun 	if (err)
487140f04c3SDavid Lebrun 		goto drop;
488140f04c3SDavid Lebrun 
489140f04c3SDavid Lebrun 	ipv6_hdr(skb)->payload_len = htons(skb->len - sizeof(struct ipv6hdr));
490140f04c3SDavid Lebrun 	skb_set_transport_header(skb, sizeof(struct ipv6hdr));
491140f04c3SDavid Lebrun 
4921c1e761eSMathieu Xhonneux 	seg6_lookup_nexthop(skb, NULL, 0);
493140f04c3SDavid Lebrun 
494140f04c3SDavid Lebrun 	return dst_input(skb);
495140f04c3SDavid Lebrun 
496140f04c3SDavid Lebrun drop:
497140f04c3SDavid Lebrun 	kfree_skb(skb);
498140f04c3SDavid Lebrun 	return err;
499140f04c3SDavid Lebrun }
500140f04c3SDavid Lebrun 
501fe94cc29SMathieu Xhonneux DEFINE_PER_CPU(struct seg6_bpf_srh_state, seg6_bpf_srh_states);
502fe94cc29SMathieu Xhonneux 
503486cdf21SMathieu Xhonneux bool seg6_bpf_has_valid_srh(struct sk_buff *skb)
504486cdf21SMathieu Xhonneux {
505486cdf21SMathieu Xhonneux 	struct seg6_bpf_srh_state *srh_state =
506486cdf21SMathieu Xhonneux 		this_cpu_ptr(&seg6_bpf_srh_states);
507486cdf21SMathieu Xhonneux 	struct ipv6_sr_hdr *srh = srh_state->srh;
508486cdf21SMathieu Xhonneux 
509486cdf21SMathieu Xhonneux 	if (unlikely(srh == NULL))
510486cdf21SMathieu Xhonneux 		return false;
511486cdf21SMathieu Xhonneux 
512486cdf21SMathieu Xhonneux 	if (unlikely(!srh_state->valid)) {
513486cdf21SMathieu Xhonneux 		if ((srh_state->hdrlen & 7) != 0)
514486cdf21SMathieu Xhonneux 			return false;
515486cdf21SMathieu Xhonneux 
516486cdf21SMathieu Xhonneux 		srh->hdrlen = (u8)(srh_state->hdrlen >> 3);
517bb986a50SAhmed Abdelsalam 		if (!seg6_validate_srh(srh, (srh->hdrlen + 1) << 3, true))
518486cdf21SMathieu Xhonneux 			return false;
519486cdf21SMathieu Xhonneux 
520486cdf21SMathieu Xhonneux 		srh_state->valid = true;
521486cdf21SMathieu Xhonneux 	}
522486cdf21SMathieu Xhonneux 
523486cdf21SMathieu Xhonneux 	return true;
524486cdf21SMathieu Xhonneux }
525486cdf21SMathieu Xhonneux 
526004d4b27SMathieu Xhonneux static int input_action_end_bpf(struct sk_buff *skb,
527004d4b27SMathieu Xhonneux 				struct seg6_local_lwt *slwt)
528004d4b27SMathieu Xhonneux {
529004d4b27SMathieu Xhonneux 	struct seg6_bpf_srh_state *srh_state =
530004d4b27SMathieu Xhonneux 		this_cpu_ptr(&seg6_bpf_srh_states);
531004d4b27SMathieu Xhonneux 	struct ipv6_sr_hdr *srh;
532004d4b27SMathieu Xhonneux 	int ret;
533004d4b27SMathieu Xhonneux 
534004d4b27SMathieu Xhonneux 	srh = get_and_validate_srh(skb);
535486cdf21SMathieu Xhonneux 	if (!srh) {
536486cdf21SMathieu Xhonneux 		kfree_skb(skb);
537486cdf21SMathieu Xhonneux 		return -EINVAL;
538486cdf21SMathieu Xhonneux 	}
539004d4b27SMathieu Xhonneux 	advance_nextseg(srh, &ipv6_hdr(skb)->daddr);
540004d4b27SMathieu Xhonneux 
541004d4b27SMathieu Xhonneux 	/* preempt_disable is needed to protect the per-CPU buffer srh_state,
542004d4b27SMathieu Xhonneux 	 * which is also accessed by the bpf_lwt_seg6_* helpers
543004d4b27SMathieu Xhonneux 	 */
544004d4b27SMathieu Xhonneux 	preempt_disable();
545486cdf21SMathieu Xhonneux 	srh_state->srh = srh;
546004d4b27SMathieu Xhonneux 	srh_state->hdrlen = srh->hdrlen << 3;
547486cdf21SMathieu Xhonneux 	srh_state->valid = true;
548004d4b27SMathieu Xhonneux 
549004d4b27SMathieu Xhonneux 	rcu_read_lock();
550004d4b27SMathieu Xhonneux 	bpf_compute_data_pointers(skb);
551004d4b27SMathieu Xhonneux 	ret = bpf_prog_run_save_cb(slwt->bpf.prog, skb);
552004d4b27SMathieu Xhonneux 	rcu_read_unlock();
553004d4b27SMathieu Xhonneux 
554004d4b27SMathieu Xhonneux 	switch (ret) {
555004d4b27SMathieu Xhonneux 	case BPF_OK:
556004d4b27SMathieu Xhonneux 	case BPF_REDIRECT:
557004d4b27SMathieu Xhonneux 		break;
558004d4b27SMathieu Xhonneux 	case BPF_DROP:
559004d4b27SMathieu Xhonneux 		goto drop;
560004d4b27SMathieu Xhonneux 	default:
561004d4b27SMathieu Xhonneux 		pr_warn_once("bpf-seg6local: Illegal return value %u\n", ret);
562004d4b27SMathieu Xhonneux 		goto drop;
563004d4b27SMathieu Xhonneux 	}
564004d4b27SMathieu Xhonneux 
565486cdf21SMathieu Xhonneux 	if (srh_state->srh && !seg6_bpf_has_valid_srh(skb))
566004d4b27SMathieu Xhonneux 		goto drop;
567004d4b27SMathieu Xhonneux 
568486cdf21SMathieu Xhonneux 	preempt_enable();
569004d4b27SMathieu Xhonneux 	if (ret != BPF_REDIRECT)
570004d4b27SMathieu Xhonneux 		seg6_lookup_nexthop(skb, NULL, 0);
571004d4b27SMathieu Xhonneux 
572004d4b27SMathieu Xhonneux 	return dst_input(skb);
573004d4b27SMathieu Xhonneux 
574004d4b27SMathieu Xhonneux drop:
575486cdf21SMathieu Xhonneux 	preempt_enable();
576004d4b27SMathieu Xhonneux 	kfree_skb(skb);
577004d4b27SMathieu Xhonneux 	return -EINVAL;
578004d4b27SMathieu Xhonneux }
579004d4b27SMathieu Xhonneux 
580d1df6fd8SDavid Lebrun static struct seg6_action_desc seg6_action_table[] = {
581d1df6fd8SDavid Lebrun 	{
582d1df6fd8SDavid Lebrun 		.action		= SEG6_LOCAL_ACTION_END,
583d1df6fd8SDavid Lebrun 		.attrs		= 0,
584140f04c3SDavid Lebrun 		.input		= input_action_end,
585d1df6fd8SDavid Lebrun 	},
586140f04c3SDavid Lebrun 	{
587140f04c3SDavid Lebrun 		.action		= SEG6_LOCAL_ACTION_END_X,
588140f04c3SDavid Lebrun 		.attrs		= (1 << SEG6_LOCAL_NH6),
589140f04c3SDavid Lebrun 		.input		= input_action_end_x,
590140f04c3SDavid Lebrun 	},
591140f04c3SDavid Lebrun 	{
592891ef8ddSDavid Lebrun 		.action		= SEG6_LOCAL_ACTION_END_T,
593891ef8ddSDavid Lebrun 		.attrs		= (1 << SEG6_LOCAL_TABLE),
594891ef8ddSDavid Lebrun 		.input		= input_action_end_t,
595891ef8ddSDavid Lebrun 	},
596891ef8ddSDavid Lebrun 	{
597891ef8ddSDavid Lebrun 		.action		= SEG6_LOCAL_ACTION_END_DX2,
598891ef8ddSDavid Lebrun 		.attrs		= (1 << SEG6_LOCAL_OIF),
599891ef8ddSDavid Lebrun 		.input		= input_action_end_dx2,
600891ef8ddSDavid Lebrun 	},
601891ef8ddSDavid Lebrun 	{
602140f04c3SDavid Lebrun 		.action		= SEG6_LOCAL_ACTION_END_DX6,
603140f04c3SDavid Lebrun 		.attrs		= (1 << SEG6_LOCAL_NH6),
604140f04c3SDavid Lebrun 		.input		= input_action_end_dx6,
605140f04c3SDavid Lebrun 	},
606140f04c3SDavid Lebrun 	{
607891ef8ddSDavid Lebrun 		.action		= SEG6_LOCAL_ACTION_END_DX4,
608891ef8ddSDavid Lebrun 		.attrs		= (1 << SEG6_LOCAL_NH4),
609891ef8ddSDavid Lebrun 		.input		= input_action_end_dx4,
610891ef8ddSDavid Lebrun 	},
611891ef8ddSDavid Lebrun 	{
612891ef8ddSDavid Lebrun 		.action		= SEG6_LOCAL_ACTION_END_DT6,
613891ef8ddSDavid Lebrun 		.attrs		= (1 << SEG6_LOCAL_TABLE),
614891ef8ddSDavid Lebrun 		.input		= input_action_end_dt6,
615891ef8ddSDavid Lebrun 	},
616891ef8ddSDavid Lebrun 	{
617140f04c3SDavid Lebrun 		.action		= SEG6_LOCAL_ACTION_END_B6,
618140f04c3SDavid Lebrun 		.attrs		= (1 << SEG6_LOCAL_SRH),
619140f04c3SDavid Lebrun 		.input		= input_action_end_b6,
620140f04c3SDavid Lebrun 	},
621140f04c3SDavid Lebrun 	{
622140f04c3SDavid Lebrun 		.action		= SEG6_LOCAL_ACTION_END_B6_ENCAP,
623140f04c3SDavid Lebrun 		.attrs		= (1 << SEG6_LOCAL_SRH),
624140f04c3SDavid Lebrun 		.input		= input_action_end_b6_encap,
625140f04c3SDavid Lebrun 		.static_headroom	= sizeof(struct ipv6hdr),
626004d4b27SMathieu Xhonneux 	},
627004d4b27SMathieu Xhonneux 	{
628004d4b27SMathieu Xhonneux 		.action		= SEG6_LOCAL_ACTION_END_BPF,
629004d4b27SMathieu Xhonneux 		.attrs		= (1 << SEG6_LOCAL_BPF),
630004d4b27SMathieu Xhonneux 		.input		= input_action_end_bpf,
631004d4b27SMathieu Xhonneux 	},
632004d4b27SMathieu Xhonneux 
633d1df6fd8SDavid Lebrun };
634d1df6fd8SDavid Lebrun 
635d1df6fd8SDavid Lebrun static struct seg6_action_desc *__get_action_desc(int action)
636d1df6fd8SDavid Lebrun {
637d1df6fd8SDavid Lebrun 	struct seg6_action_desc *desc;
638d1df6fd8SDavid Lebrun 	int i, count;
639d1df6fd8SDavid Lebrun 
640709af180SColin Ian King 	count = ARRAY_SIZE(seg6_action_table);
641d1df6fd8SDavid Lebrun 	for (i = 0; i < count; i++) {
642d1df6fd8SDavid Lebrun 		desc = &seg6_action_table[i];
643d1df6fd8SDavid Lebrun 		if (desc->action == action)
644d1df6fd8SDavid Lebrun 			return desc;
645d1df6fd8SDavid Lebrun 	}
646d1df6fd8SDavid Lebrun 
647d1df6fd8SDavid Lebrun 	return NULL;
648d1df6fd8SDavid Lebrun }
649d1df6fd8SDavid Lebrun 
650d1df6fd8SDavid Lebrun static int seg6_local_input(struct sk_buff *skb)
651d1df6fd8SDavid Lebrun {
652d1df6fd8SDavid Lebrun 	struct dst_entry *orig_dst = skb_dst(skb);
653d1df6fd8SDavid Lebrun 	struct seg6_action_desc *desc;
654d1df6fd8SDavid Lebrun 	struct seg6_local_lwt *slwt;
655d1df6fd8SDavid Lebrun 
6566285217fSDavid Lebrun 	if (skb->protocol != htons(ETH_P_IPV6)) {
6576285217fSDavid Lebrun 		kfree_skb(skb);
6586285217fSDavid Lebrun 		return -EINVAL;
6596285217fSDavid Lebrun 	}
6606285217fSDavid Lebrun 
661d1df6fd8SDavid Lebrun 	slwt = seg6_local_lwtunnel(orig_dst->lwtstate);
662d1df6fd8SDavid Lebrun 	desc = slwt->desc;
663d1df6fd8SDavid Lebrun 
664d1df6fd8SDavid Lebrun 	return desc->input(skb, slwt);
665d1df6fd8SDavid Lebrun }
666d1df6fd8SDavid Lebrun 
667d1df6fd8SDavid Lebrun static const struct nla_policy seg6_local_policy[SEG6_LOCAL_MAX + 1] = {
668d1df6fd8SDavid Lebrun 	[SEG6_LOCAL_ACTION]	= { .type = NLA_U32 },
669d1df6fd8SDavid Lebrun 	[SEG6_LOCAL_SRH]	= { .type = NLA_BINARY },
670d1df6fd8SDavid Lebrun 	[SEG6_LOCAL_TABLE]	= { .type = NLA_U32 },
671d1df6fd8SDavid Lebrun 	[SEG6_LOCAL_NH4]	= { .type = NLA_BINARY,
672d1df6fd8SDavid Lebrun 				    .len = sizeof(struct in_addr) },
673d1df6fd8SDavid Lebrun 	[SEG6_LOCAL_NH6]	= { .type = NLA_BINARY,
674d1df6fd8SDavid Lebrun 				    .len = sizeof(struct in6_addr) },
675d1df6fd8SDavid Lebrun 	[SEG6_LOCAL_IIF]	= { .type = NLA_U32 },
676d1df6fd8SDavid Lebrun 	[SEG6_LOCAL_OIF]	= { .type = NLA_U32 },
677004d4b27SMathieu Xhonneux 	[SEG6_LOCAL_BPF]	= { .type = NLA_NESTED },
678d1df6fd8SDavid Lebrun };
679d1df6fd8SDavid Lebrun 
6802d9cc60aSDavid Lebrun static int parse_nla_srh(struct nlattr **attrs, struct seg6_local_lwt *slwt)
6812d9cc60aSDavid Lebrun {
6822d9cc60aSDavid Lebrun 	struct ipv6_sr_hdr *srh;
6832d9cc60aSDavid Lebrun 	int len;
6842d9cc60aSDavid Lebrun 
6852d9cc60aSDavid Lebrun 	srh = nla_data(attrs[SEG6_LOCAL_SRH]);
6862d9cc60aSDavid Lebrun 	len = nla_len(attrs[SEG6_LOCAL_SRH]);
6872d9cc60aSDavid Lebrun 
6882d9cc60aSDavid Lebrun 	/* SRH must contain at least one segment */
6892d9cc60aSDavid Lebrun 	if (len < sizeof(*srh) + sizeof(struct in6_addr))
6902d9cc60aSDavid Lebrun 		return -EINVAL;
6912d9cc60aSDavid Lebrun 
692bb986a50SAhmed Abdelsalam 	if (!seg6_validate_srh(srh, len, false))
6932d9cc60aSDavid Lebrun 		return -EINVAL;
6942d9cc60aSDavid Lebrun 
6957fa41efaSYueHaibing 	slwt->srh = kmemdup(srh, len, GFP_KERNEL);
6962d9cc60aSDavid Lebrun 	if (!slwt->srh)
6972d9cc60aSDavid Lebrun 		return -ENOMEM;
6982d9cc60aSDavid Lebrun 
6992d9cc60aSDavid Lebrun 	slwt->headroom += len;
7002d9cc60aSDavid Lebrun 
7012d9cc60aSDavid Lebrun 	return 0;
7022d9cc60aSDavid Lebrun }
7032d9cc60aSDavid Lebrun 
7042d9cc60aSDavid Lebrun static int put_nla_srh(struct sk_buff *skb, struct seg6_local_lwt *slwt)
7052d9cc60aSDavid Lebrun {
7062d9cc60aSDavid Lebrun 	struct ipv6_sr_hdr *srh;
7072d9cc60aSDavid Lebrun 	struct nlattr *nla;
7082d9cc60aSDavid Lebrun 	int len;
7092d9cc60aSDavid Lebrun 
7102d9cc60aSDavid Lebrun 	srh = slwt->srh;
7112d9cc60aSDavid Lebrun 	len = (srh->hdrlen + 1) << 3;
7122d9cc60aSDavid Lebrun 
7132d9cc60aSDavid Lebrun 	nla = nla_reserve(skb, SEG6_LOCAL_SRH, len);
7142d9cc60aSDavid Lebrun 	if (!nla)
7152d9cc60aSDavid Lebrun 		return -EMSGSIZE;
7162d9cc60aSDavid Lebrun 
7172d9cc60aSDavid Lebrun 	memcpy(nla_data(nla), srh, len);
7182d9cc60aSDavid Lebrun 
7192d9cc60aSDavid Lebrun 	return 0;
7202d9cc60aSDavid Lebrun }
7212d9cc60aSDavid Lebrun 
7222d9cc60aSDavid Lebrun static int cmp_nla_srh(struct seg6_local_lwt *a, struct seg6_local_lwt *b)
7232d9cc60aSDavid Lebrun {
7242d9cc60aSDavid Lebrun 	int len = (a->srh->hdrlen + 1) << 3;
7252d9cc60aSDavid Lebrun 
7262d9cc60aSDavid Lebrun 	if (len != ((b->srh->hdrlen + 1) << 3))
7272d9cc60aSDavid Lebrun 		return 1;
7282d9cc60aSDavid Lebrun 
7292d9cc60aSDavid Lebrun 	return memcmp(a->srh, b->srh, len);
7302d9cc60aSDavid Lebrun }
7312d9cc60aSDavid Lebrun 
732964adce5SAndrea Mayer static void destroy_attr_srh(struct seg6_local_lwt *slwt)
733964adce5SAndrea Mayer {
734964adce5SAndrea Mayer 	kfree(slwt->srh);
735964adce5SAndrea Mayer }
736964adce5SAndrea Mayer 
7372d9cc60aSDavid Lebrun static int parse_nla_table(struct nlattr **attrs, struct seg6_local_lwt *slwt)
7382d9cc60aSDavid Lebrun {
7392d9cc60aSDavid Lebrun 	slwt->table = nla_get_u32(attrs[SEG6_LOCAL_TABLE]);
7402d9cc60aSDavid Lebrun 
7412d9cc60aSDavid Lebrun 	return 0;
7422d9cc60aSDavid Lebrun }
7432d9cc60aSDavid Lebrun 
7442d9cc60aSDavid Lebrun static int put_nla_table(struct sk_buff *skb, struct seg6_local_lwt *slwt)
7452d9cc60aSDavid Lebrun {
7462d9cc60aSDavid Lebrun 	if (nla_put_u32(skb, SEG6_LOCAL_TABLE, slwt->table))
7472d9cc60aSDavid Lebrun 		return -EMSGSIZE;
7482d9cc60aSDavid Lebrun 
7492d9cc60aSDavid Lebrun 	return 0;
7502d9cc60aSDavid Lebrun }
7512d9cc60aSDavid Lebrun 
7522d9cc60aSDavid Lebrun static int cmp_nla_table(struct seg6_local_lwt *a, struct seg6_local_lwt *b)
7532d9cc60aSDavid Lebrun {
7542d9cc60aSDavid Lebrun 	if (a->table != b->table)
7552d9cc60aSDavid Lebrun 		return 1;
7562d9cc60aSDavid Lebrun 
7572d9cc60aSDavid Lebrun 	return 0;
7582d9cc60aSDavid Lebrun }
7592d9cc60aSDavid Lebrun 
7602d9cc60aSDavid Lebrun static int parse_nla_nh4(struct nlattr **attrs, struct seg6_local_lwt *slwt)
7612d9cc60aSDavid Lebrun {
7622d9cc60aSDavid Lebrun 	memcpy(&slwt->nh4, nla_data(attrs[SEG6_LOCAL_NH4]),
7632d9cc60aSDavid Lebrun 	       sizeof(struct in_addr));
7642d9cc60aSDavid Lebrun 
7652d9cc60aSDavid Lebrun 	return 0;
7662d9cc60aSDavid Lebrun }
7672d9cc60aSDavid Lebrun 
7682d9cc60aSDavid Lebrun static int put_nla_nh4(struct sk_buff *skb, struct seg6_local_lwt *slwt)
7692d9cc60aSDavid Lebrun {
7702d9cc60aSDavid Lebrun 	struct nlattr *nla;
7712d9cc60aSDavid Lebrun 
7722d9cc60aSDavid Lebrun 	nla = nla_reserve(skb, SEG6_LOCAL_NH4, sizeof(struct in_addr));
7732d9cc60aSDavid Lebrun 	if (!nla)
7742d9cc60aSDavid Lebrun 		return -EMSGSIZE;
7752d9cc60aSDavid Lebrun 
7762d9cc60aSDavid Lebrun 	memcpy(nla_data(nla), &slwt->nh4, sizeof(struct in_addr));
7772d9cc60aSDavid Lebrun 
7782d9cc60aSDavid Lebrun 	return 0;
7792d9cc60aSDavid Lebrun }
7802d9cc60aSDavid Lebrun 
7812d9cc60aSDavid Lebrun static int cmp_nla_nh4(struct seg6_local_lwt *a, struct seg6_local_lwt *b)
7822d9cc60aSDavid Lebrun {
7832d9cc60aSDavid Lebrun 	return memcmp(&a->nh4, &b->nh4, sizeof(struct in_addr));
7842d9cc60aSDavid Lebrun }
7852d9cc60aSDavid Lebrun 
7862d9cc60aSDavid Lebrun static int parse_nla_nh6(struct nlattr **attrs, struct seg6_local_lwt *slwt)
7872d9cc60aSDavid Lebrun {
7882d9cc60aSDavid Lebrun 	memcpy(&slwt->nh6, nla_data(attrs[SEG6_LOCAL_NH6]),
7892d9cc60aSDavid Lebrun 	       sizeof(struct in6_addr));
7902d9cc60aSDavid Lebrun 
7912d9cc60aSDavid Lebrun 	return 0;
7922d9cc60aSDavid Lebrun }
7932d9cc60aSDavid Lebrun 
7942d9cc60aSDavid Lebrun static int put_nla_nh6(struct sk_buff *skb, struct seg6_local_lwt *slwt)
7952d9cc60aSDavid Lebrun {
7962d9cc60aSDavid Lebrun 	struct nlattr *nla;
7972d9cc60aSDavid Lebrun 
7982d9cc60aSDavid Lebrun 	nla = nla_reserve(skb, SEG6_LOCAL_NH6, sizeof(struct in6_addr));
7992d9cc60aSDavid Lebrun 	if (!nla)
8002d9cc60aSDavid Lebrun 		return -EMSGSIZE;
8012d9cc60aSDavid Lebrun 
8022d9cc60aSDavid Lebrun 	memcpy(nla_data(nla), &slwt->nh6, sizeof(struct in6_addr));
8032d9cc60aSDavid Lebrun 
8042d9cc60aSDavid Lebrun 	return 0;
8052d9cc60aSDavid Lebrun }
8062d9cc60aSDavid Lebrun 
8072d9cc60aSDavid Lebrun static int cmp_nla_nh6(struct seg6_local_lwt *a, struct seg6_local_lwt *b)
8082d9cc60aSDavid Lebrun {
8092d9cc60aSDavid Lebrun 	return memcmp(&a->nh6, &b->nh6, sizeof(struct in6_addr));
8102d9cc60aSDavid Lebrun }
8112d9cc60aSDavid Lebrun 
8122d9cc60aSDavid Lebrun static int parse_nla_iif(struct nlattr **attrs, struct seg6_local_lwt *slwt)
8132d9cc60aSDavid Lebrun {
8142d9cc60aSDavid Lebrun 	slwt->iif = nla_get_u32(attrs[SEG6_LOCAL_IIF]);
8152d9cc60aSDavid Lebrun 
8162d9cc60aSDavid Lebrun 	return 0;
8172d9cc60aSDavid Lebrun }
8182d9cc60aSDavid Lebrun 
8192d9cc60aSDavid Lebrun static int put_nla_iif(struct sk_buff *skb, struct seg6_local_lwt *slwt)
8202d9cc60aSDavid Lebrun {
8212d9cc60aSDavid Lebrun 	if (nla_put_u32(skb, SEG6_LOCAL_IIF, slwt->iif))
8222d9cc60aSDavid Lebrun 		return -EMSGSIZE;
8232d9cc60aSDavid Lebrun 
8242d9cc60aSDavid Lebrun 	return 0;
8252d9cc60aSDavid Lebrun }
8262d9cc60aSDavid Lebrun 
8272d9cc60aSDavid Lebrun static int cmp_nla_iif(struct seg6_local_lwt *a, struct seg6_local_lwt *b)
8282d9cc60aSDavid Lebrun {
8292d9cc60aSDavid Lebrun 	if (a->iif != b->iif)
8302d9cc60aSDavid Lebrun 		return 1;
8312d9cc60aSDavid Lebrun 
8322d9cc60aSDavid Lebrun 	return 0;
8332d9cc60aSDavid Lebrun }
8342d9cc60aSDavid Lebrun 
8352d9cc60aSDavid Lebrun static int parse_nla_oif(struct nlattr **attrs, struct seg6_local_lwt *slwt)
8362d9cc60aSDavid Lebrun {
8372d9cc60aSDavid Lebrun 	slwt->oif = nla_get_u32(attrs[SEG6_LOCAL_OIF]);
8382d9cc60aSDavid Lebrun 
8392d9cc60aSDavid Lebrun 	return 0;
8402d9cc60aSDavid Lebrun }
8412d9cc60aSDavid Lebrun 
8422d9cc60aSDavid Lebrun static int put_nla_oif(struct sk_buff *skb, struct seg6_local_lwt *slwt)
8432d9cc60aSDavid Lebrun {
8442d9cc60aSDavid Lebrun 	if (nla_put_u32(skb, SEG6_LOCAL_OIF, slwt->oif))
8452d9cc60aSDavid Lebrun 		return -EMSGSIZE;
8462d9cc60aSDavid Lebrun 
8472d9cc60aSDavid Lebrun 	return 0;
8482d9cc60aSDavid Lebrun }
8492d9cc60aSDavid Lebrun 
8502d9cc60aSDavid Lebrun static int cmp_nla_oif(struct seg6_local_lwt *a, struct seg6_local_lwt *b)
8512d9cc60aSDavid Lebrun {
8522d9cc60aSDavid Lebrun 	if (a->oif != b->oif)
8532d9cc60aSDavid Lebrun 		return 1;
8542d9cc60aSDavid Lebrun 
8552d9cc60aSDavid Lebrun 	return 0;
8562d9cc60aSDavid Lebrun }
8572d9cc60aSDavid Lebrun 
858004d4b27SMathieu Xhonneux #define MAX_PROG_NAME 256
859004d4b27SMathieu Xhonneux static const struct nla_policy bpf_prog_policy[SEG6_LOCAL_BPF_PROG_MAX + 1] = {
860004d4b27SMathieu Xhonneux 	[SEG6_LOCAL_BPF_PROG]	   = { .type = NLA_U32, },
861004d4b27SMathieu Xhonneux 	[SEG6_LOCAL_BPF_PROG_NAME] = { .type = NLA_NUL_STRING,
862004d4b27SMathieu Xhonneux 				       .len = MAX_PROG_NAME },
863004d4b27SMathieu Xhonneux };
864004d4b27SMathieu Xhonneux 
865004d4b27SMathieu Xhonneux static int parse_nla_bpf(struct nlattr **attrs, struct seg6_local_lwt *slwt)
866004d4b27SMathieu Xhonneux {
867004d4b27SMathieu Xhonneux 	struct nlattr *tb[SEG6_LOCAL_BPF_PROG_MAX + 1];
868004d4b27SMathieu Xhonneux 	struct bpf_prog *p;
869004d4b27SMathieu Xhonneux 	int ret;
870004d4b27SMathieu Xhonneux 	u32 fd;
871004d4b27SMathieu Xhonneux 
8728cb08174SJohannes Berg 	ret = nla_parse_nested_deprecated(tb, SEG6_LOCAL_BPF_PROG_MAX,
8738cb08174SJohannes Berg 					  attrs[SEG6_LOCAL_BPF],
8748cb08174SJohannes Berg 					  bpf_prog_policy, NULL);
875004d4b27SMathieu Xhonneux 	if (ret < 0)
876004d4b27SMathieu Xhonneux 		return ret;
877004d4b27SMathieu Xhonneux 
878004d4b27SMathieu Xhonneux 	if (!tb[SEG6_LOCAL_BPF_PROG] || !tb[SEG6_LOCAL_BPF_PROG_NAME])
879004d4b27SMathieu Xhonneux 		return -EINVAL;
880004d4b27SMathieu Xhonneux 
881004d4b27SMathieu Xhonneux 	slwt->bpf.name = nla_memdup(tb[SEG6_LOCAL_BPF_PROG_NAME], GFP_KERNEL);
882004d4b27SMathieu Xhonneux 	if (!slwt->bpf.name)
883004d4b27SMathieu Xhonneux 		return -ENOMEM;
884004d4b27SMathieu Xhonneux 
885004d4b27SMathieu Xhonneux 	fd = nla_get_u32(tb[SEG6_LOCAL_BPF_PROG]);
886004d4b27SMathieu Xhonneux 	p = bpf_prog_get_type(fd, BPF_PROG_TYPE_LWT_SEG6LOCAL);
887004d4b27SMathieu Xhonneux 	if (IS_ERR(p)) {
888004d4b27SMathieu Xhonneux 		kfree(slwt->bpf.name);
889004d4b27SMathieu Xhonneux 		return PTR_ERR(p);
890004d4b27SMathieu Xhonneux 	}
891004d4b27SMathieu Xhonneux 
892004d4b27SMathieu Xhonneux 	slwt->bpf.prog = p;
893004d4b27SMathieu Xhonneux 	return 0;
894004d4b27SMathieu Xhonneux }
895004d4b27SMathieu Xhonneux 
896004d4b27SMathieu Xhonneux static int put_nla_bpf(struct sk_buff *skb, struct seg6_local_lwt *slwt)
897004d4b27SMathieu Xhonneux {
898004d4b27SMathieu Xhonneux 	struct nlattr *nest;
899004d4b27SMathieu Xhonneux 
900004d4b27SMathieu Xhonneux 	if (!slwt->bpf.prog)
901004d4b27SMathieu Xhonneux 		return 0;
902004d4b27SMathieu Xhonneux 
903ae0be8deSMichal Kubecek 	nest = nla_nest_start_noflag(skb, SEG6_LOCAL_BPF);
904004d4b27SMathieu Xhonneux 	if (!nest)
905004d4b27SMathieu Xhonneux 		return -EMSGSIZE;
906004d4b27SMathieu Xhonneux 
907004d4b27SMathieu Xhonneux 	if (nla_put_u32(skb, SEG6_LOCAL_BPF_PROG, slwt->bpf.prog->aux->id))
908004d4b27SMathieu Xhonneux 		return -EMSGSIZE;
909004d4b27SMathieu Xhonneux 
910004d4b27SMathieu Xhonneux 	if (slwt->bpf.name &&
911004d4b27SMathieu Xhonneux 	    nla_put_string(skb, SEG6_LOCAL_BPF_PROG_NAME, slwt->bpf.name))
912004d4b27SMathieu Xhonneux 		return -EMSGSIZE;
913004d4b27SMathieu Xhonneux 
914004d4b27SMathieu Xhonneux 	return nla_nest_end(skb, nest);
915004d4b27SMathieu Xhonneux }
916004d4b27SMathieu Xhonneux 
917004d4b27SMathieu Xhonneux static int cmp_nla_bpf(struct seg6_local_lwt *a, struct seg6_local_lwt *b)
918004d4b27SMathieu Xhonneux {
919004d4b27SMathieu Xhonneux 	if (!a->bpf.name && !b->bpf.name)
920004d4b27SMathieu Xhonneux 		return 0;
921004d4b27SMathieu Xhonneux 
922004d4b27SMathieu Xhonneux 	if (!a->bpf.name || !b->bpf.name)
923004d4b27SMathieu Xhonneux 		return 1;
924004d4b27SMathieu Xhonneux 
925004d4b27SMathieu Xhonneux 	return strcmp(a->bpf.name, b->bpf.name);
926004d4b27SMathieu Xhonneux }
927004d4b27SMathieu Xhonneux 
928964adce5SAndrea Mayer static void destroy_attr_bpf(struct seg6_local_lwt *slwt)
929964adce5SAndrea Mayer {
930964adce5SAndrea Mayer 	kfree(slwt->bpf.name);
931964adce5SAndrea Mayer 	if (slwt->bpf.prog)
932964adce5SAndrea Mayer 		bpf_prog_put(slwt->bpf.prog);
933964adce5SAndrea Mayer }
934964adce5SAndrea Mayer 
935d1df6fd8SDavid Lebrun struct seg6_action_param {
936d1df6fd8SDavid Lebrun 	int (*parse)(struct nlattr **attrs, struct seg6_local_lwt *slwt);
937d1df6fd8SDavid Lebrun 	int (*put)(struct sk_buff *skb, struct seg6_local_lwt *slwt);
938d1df6fd8SDavid Lebrun 	int (*cmp)(struct seg6_local_lwt *a, struct seg6_local_lwt *b);
939964adce5SAndrea Mayer 
940964adce5SAndrea Mayer 	/* optional destroy() callback useful for releasing resources which
941964adce5SAndrea Mayer 	 * have been previously acquired in the corresponding parse()
942964adce5SAndrea Mayer 	 * function.
943964adce5SAndrea Mayer 	 */
944964adce5SAndrea Mayer 	void (*destroy)(struct seg6_local_lwt *slwt);
945d1df6fd8SDavid Lebrun };
946d1df6fd8SDavid Lebrun 
947d1df6fd8SDavid Lebrun static struct seg6_action_param seg6_action_params[SEG6_LOCAL_MAX + 1] = {
9482d9cc60aSDavid Lebrun 	[SEG6_LOCAL_SRH]	= { .parse = parse_nla_srh,
9492d9cc60aSDavid Lebrun 				    .put = put_nla_srh,
950964adce5SAndrea Mayer 				    .cmp = cmp_nla_srh,
951964adce5SAndrea Mayer 				    .destroy = destroy_attr_srh },
952d1df6fd8SDavid Lebrun 
9532d9cc60aSDavid Lebrun 	[SEG6_LOCAL_TABLE]	= { .parse = parse_nla_table,
9542d9cc60aSDavid Lebrun 				    .put = put_nla_table,
9552d9cc60aSDavid Lebrun 				    .cmp = cmp_nla_table },
956d1df6fd8SDavid Lebrun 
9572d9cc60aSDavid Lebrun 	[SEG6_LOCAL_NH4]	= { .parse = parse_nla_nh4,
9582d9cc60aSDavid Lebrun 				    .put = put_nla_nh4,
9592d9cc60aSDavid Lebrun 				    .cmp = cmp_nla_nh4 },
960d1df6fd8SDavid Lebrun 
9612d9cc60aSDavid Lebrun 	[SEG6_LOCAL_NH6]	= { .parse = parse_nla_nh6,
9622d9cc60aSDavid Lebrun 				    .put = put_nla_nh6,
9632d9cc60aSDavid Lebrun 				    .cmp = cmp_nla_nh6 },
964d1df6fd8SDavid Lebrun 
9652d9cc60aSDavid Lebrun 	[SEG6_LOCAL_IIF]	= { .parse = parse_nla_iif,
9662d9cc60aSDavid Lebrun 				    .put = put_nla_iif,
9672d9cc60aSDavid Lebrun 				    .cmp = cmp_nla_iif },
968d1df6fd8SDavid Lebrun 
9692d9cc60aSDavid Lebrun 	[SEG6_LOCAL_OIF]	= { .parse = parse_nla_oif,
9702d9cc60aSDavid Lebrun 				    .put = put_nla_oif,
9712d9cc60aSDavid Lebrun 				    .cmp = cmp_nla_oif },
972004d4b27SMathieu Xhonneux 
973004d4b27SMathieu Xhonneux 	[SEG6_LOCAL_BPF]	= { .parse = parse_nla_bpf,
974004d4b27SMathieu Xhonneux 				    .put = put_nla_bpf,
975964adce5SAndrea Mayer 				    .cmp = cmp_nla_bpf,
976964adce5SAndrea Mayer 				    .destroy = destroy_attr_bpf },
977004d4b27SMathieu Xhonneux 
978d1df6fd8SDavid Lebrun };
979d1df6fd8SDavid Lebrun 
980964adce5SAndrea Mayer /* call the destroy() callback (if available) for each set attribute in
981*0a3021f1SAndrea Mayer  * @parsed_attrs, starting from the first attribute up to the @max_parsed
982*0a3021f1SAndrea Mayer  * (excluded) attribute.
983964adce5SAndrea Mayer  */
984*0a3021f1SAndrea Mayer static void __destroy_attrs(unsigned long parsed_attrs, int max_parsed,
985*0a3021f1SAndrea Mayer 			    struct seg6_local_lwt *slwt)
986964adce5SAndrea Mayer {
987964adce5SAndrea Mayer 	struct seg6_action_param *param;
988964adce5SAndrea Mayer 	int i;
989964adce5SAndrea Mayer 
990964adce5SAndrea Mayer 	/* Every required seg6local attribute is identified by an ID which is
991964adce5SAndrea Mayer 	 * encoded as a flag (i.e: 1 << ID) in the 'attrs' bitmask;
992964adce5SAndrea Mayer 	 *
993*0a3021f1SAndrea Mayer 	 * We scan the 'parsed_attrs' bitmask, starting from the first attribute
994964adce5SAndrea Mayer 	 * up to the @max_parsed (excluded) attribute.
995964adce5SAndrea Mayer 	 * For each set attribute, we retrieve the corresponding destroy()
996964adce5SAndrea Mayer 	 * callback. If the callback is not available, then we skip to the next
997964adce5SAndrea Mayer 	 * attribute; otherwise, we call the destroy() callback.
998964adce5SAndrea Mayer 	 */
999964adce5SAndrea Mayer 	for (i = 0; i < max_parsed; ++i) {
1000*0a3021f1SAndrea Mayer 		if (!(parsed_attrs & (1 << i)))
1001964adce5SAndrea Mayer 			continue;
1002964adce5SAndrea Mayer 
1003964adce5SAndrea Mayer 		param = &seg6_action_params[i];
1004964adce5SAndrea Mayer 
1005964adce5SAndrea Mayer 		if (param->destroy)
1006964adce5SAndrea Mayer 			param->destroy(slwt);
1007964adce5SAndrea Mayer 	}
1008964adce5SAndrea Mayer }
1009964adce5SAndrea Mayer 
1010964adce5SAndrea Mayer /* release all the resources that may have been acquired during parsing
1011964adce5SAndrea Mayer  * operations.
1012964adce5SAndrea Mayer  */
1013964adce5SAndrea Mayer static void destroy_attrs(struct seg6_local_lwt *slwt)
1014964adce5SAndrea Mayer {
1015*0a3021f1SAndrea Mayer 	unsigned long attrs = slwt->desc->attrs | slwt->parsed_optattrs;
1016*0a3021f1SAndrea Mayer 
1017*0a3021f1SAndrea Mayer 	__destroy_attrs(attrs, SEG6_LOCAL_MAX + 1, slwt);
1018*0a3021f1SAndrea Mayer }
1019*0a3021f1SAndrea Mayer 
1020*0a3021f1SAndrea Mayer static int parse_nla_optional_attrs(struct nlattr **attrs,
1021*0a3021f1SAndrea Mayer 				    struct seg6_local_lwt *slwt)
1022*0a3021f1SAndrea Mayer {
1023*0a3021f1SAndrea Mayer 	struct seg6_action_desc *desc = slwt->desc;
1024*0a3021f1SAndrea Mayer 	unsigned long parsed_optattrs = 0;
1025*0a3021f1SAndrea Mayer 	struct seg6_action_param *param;
1026*0a3021f1SAndrea Mayer 	int err, i;
1027*0a3021f1SAndrea Mayer 
1028*0a3021f1SAndrea Mayer 	for (i = 0; i < SEG6_LOCAL_MAX + 1; ++i) {
1029*0a3021f1SAndrea Mayer 		if (!(desc->optattrs & (1 << i)) || !attrs[i])
1030*0a3021f1SAndrea Mayer 			continue;
1031*0a3021f1SAndrea Mayer 
1032*0a3021f1SAndrea Mayer 		/* once here, the i-th attribute is provided by the
1033*0a3021f1SAndrea Mayer 		 * userspace AND it is identified optional as well.
1034*0a3021f1SAndrea Mayer 		 */
1035*0a3021f1SAndrea Mayer 		param = &seg6_action_params[i];
1036*0a3021f1SAndrea Mayer 
1037*0a3021f1SAndrea Mayer 		err = param->parse(attrs, slwt);
1038*0a3021f1SAndrea Mayer 		if (err < 0)
1039*0a3021f1SAndrea Mayer 			goto parse_optattrs_err;
1040*0a3021f1SAndrea Mayer 
1041*0a3021f1SAndrea Mayer 		/* current attribute has been correctly parsed */
1042*0a3021f1SAndrea Mayer 		parsed_optattrs |= (1 << i);
1043*0a3021f1SAndrea Mayer 	}
1044*0a3021f1SAndrea Mayer 
1045*0a3021f1SAndrea Mayer 	/* store in the tunnel state all the optional attributed successfully
1046*0a3021f1SAndrea Mayer 	 * parsed.
1047*0a3021f1SAndrea Mayer 	 */
1048*0a3021f1SAndrea Mayer 	slwt->parsed_optattrs = parsed_optattrs;
1049*0a3021f1SAndrea Mayer 
1050*0a3021f1SAndrea Mayer 	return 0;
1051*0a3021f1SAndrea Mayer 
1052*0a3021f1SAndrea Mayer parse_optattrs_err:
1053*0a3021f1SAndrea Mayer 	__destroy_attrs(parsed_optattrs, i, slwt);
1054*0a3021f1SAndrea Mayer 
1055*0a3021f1SAndrea Mayer 	return err;
1056964adce5SAndrea Mayer }
1057964adce5SAndrea Mayer 
1058d1df6fd8SDavid Lebrun static int parse_nla_action(struct nlattr **attrs, struct seg6_local_lwt *slwt)
1059d1df6fd8SDavid Lebrun {
1060d1df6fd8SDavid Lebrun 	struct seg6_action_param *param;
1061d1df6fd8SDavid Lebrun 	struct seg6_action_desc *desc;
1062*0a3021f1SAndrea Mayer 	unsigned long invalid_attrs;
1063d1df6fd8SDavid Lebrun 	int i, err;
1064d1df6fd8SDavid Lebrun 
1065d1df6fd8SDavid Lebrun 	desc = __get_action_desc(slwt->action);
1066d1df6fd8SDavid Lebrun 	if (!desc)
1067d1df6fd8SDavid Lebrun 		return -EINVAL;
1068d1df6fd8SDavid Lebrun 
1069d1df6fd8SDavid Lebrun 	if (!desc->input)
1070d1df6fd8SDavid Lebrun 		return -EOPNOTSUPP;
1071d1df6fd8SDavid Lebrun 
1072d1df6fd8SDavid Lebrun 	slwt->desc = desc;
1073d1df6fd8SDavid Lebrun 	slwt->headroom += desc->static_headroom;
1074d1df6fd8SDavid Lebrun 
1075*0a3021f1SAndrea Mayer 	/* Forcing the desc->optattrs *set* and the desc->attrs *set* to be
1076*0a3021f1SAndrea Mayer 	 * disjoined, this allow us to release acquired resources by optional
1077*0a3021f1SAndrea Mayer 	 * attributes and by required attributes independently from each other
1078*0a3021f1SAndrea Mayer 	 * without any interfarence.
1079*0a3021f1SAndrea Mayer 	 * In other terms, we are sure that we do not release some the acquired
1080*0a3021f1SAndrea Mayer 	 * resources twice.
1081*0a3021f1SAndrea Mayer 	 *
1082*0a3021f1SAndrea Mayer 	 * Note that if an attribute is configured both as required and as
1083*0a3021f1SAndrea Mayer 	 * optional, it means that the user has messed something up in the
1084*0a3021f1SAndrea Mayer 	 * seg6_action_table. Therefore, this check is required for SRv6
1085*0a3021f1SAndrea Mayer 	 * behaviors to work properly.
1086*0a3021f1SAndrea Mayer 	 */
1087*0a3021f1SAndrea Mayer 	invalid_attrs = desc->attrs & desc->optattrs;
1088*0a3021f1SAndrea Mayer 	if (invalid_attrs) {
1089*0a3021f1SAndrea Mayer 		WARN_ONCE(1,
1090*0a3021f1SAndrea Mayer 			  "An attribute cannot be both required AND optional");
1091*0a3021f1SAndrea Mayer 		return -EINVAL;
1092*0a3021f1SAndrea Mayer 	}
1093*0a3021f1SAndrea Mayer 
1094*0a3021f1SAndrea Mayer 	/* parse the required attributes */
1095d1df6fd8SDavid Lebrun 	for (i = 0; i < SEG6_LOCAL_MAX + 1; i++) {
1096d1df6fd8SDavid Lebrun 		if (desc->attrs & (1 << i)) {
1097d1df6fd8SDavid Lebrun 			if (!attrs[i])
1098d1df6fd8SDavid Lebrun 				return -EINVAL;
1099d1df6fd8SDavid Lebrun 
1100d1df6fd8SDavid Lebrun 			param = &seg6_action_params[i];
1101d1df6fd8SDavid Lebrun 
1102d1df6fd8SDavid Lebrun 			err = param->parse(attrs, slwt);
1103d1df6fd8SDavid Lebrun 			if (err < 0)
1104*0a3021f1SAndrea Mayer 				goto parse_attrs_err;
1105d1df6fd8SDavid Lebrun 		}
1106d1df6fd8SDavid Lebrun 	}
1107d1df6fd8SDavid Lebrun 
1108*0a3021f1SAndrea Mayer 	/* parse the optional attributes, if any */
1109*0a3021f1SAndrea Mayer 	err = parse_nla_optional_attrs(attrs, slwt);
1110*0a3021f1SAndrea Mayer 	if (err < 0)
1111*0a3021f1SAndrea Mayer 		goto parse_attrs_err;
1112*0a3021f1SAndrea Mayer 
1113d1df6fd8SDavid Lebrun 	return 0;
1114964adce5SAndrea Mayer 
1115*0a3021f1SAndrea Mayer parse_attrs_err:
1116964adce5SAndrea Mayer 	/* release any resource that may have been acquired during the i-1
1117964adce5SAndrea Mayer 	 * parse() operations.
1118964adce5SAndrea Mayer 	 */
1119*0a3021f1SAndrea Mayer 	__destroy_attrs(desc->attrs, i, slwt);
1120964adce5SAndrea Mayer 
1121964adce5SAndrea Mayer 	return err;
1122d1df6fd8SDavid Lebrun }
1123d1df6fd8SDavid Lebrun 
1124faee6769SAlexander Aring static int seg6_local_build_state(struct net *net, struct nlattr *nla,
1125faee6769SAlexander Aring 				  unsigned int family, const void *cfg,
1126faee6769SAlexander Aring 				  struct lwtunnel_state **ts,
1127d1df6fd8SDavid Lebrun 				  struct netlink_ext_ack *extack)
1128d1df6fd8SDavid Lebrun {
1129d1df6fd8SDavid Lebrun 	struct nlattr *tb[SEG6_LOCAL_MAX + 1];
1130d1df6fd8SDavid Lebrun 	struct lwtunnel_state *newts;
1131d1df6fd8SDavid Lebrun 	struct seg6_local_lwt *slwt;
1132d1df6fd8SDavid Lebrun 	int err;
1133d1df6fd8SDavid Lebrun 
11346285217fSDavid Lebrun 	if (family != AF_INET6)
11356285217fSDavid Lebrun 		return -EINVAL;
11366285217fSDavid Lebrun 
11378cb08174SJohannes Berg 	err = nla_parse_nested_deprecated(tb, SEG6_LOCAL_MAX, nla,
11388cb08174SJohannes Berg 					  seg6_local_policy, extack);
1139d1df6fd8SDavid Lebrun 
1140d1df6fd8SDavid Lebrun 	if (err < 0)
1141d1df6fd8SDavid Lebrun 		return err;
1142d1df6fd8SDavid Lebrun 
1143d1df6fd8SDavid Lebrun 	if (!tb[SEG6_LOCAL_ACTION])
1144d1df6fd8SDavid Lebrun 		return -EINVAL;
1145d1df6fd8SDavid Lebrun 
1146d1df6fd8SDavid Lebrun 	newts = lwtunnel_state_alloc(sizeof(*slwt));
1147d1df6fd8SDavid Lebrun 	if (!newts)
1148d1df6fd8SDavid Lebrun 		return -ENOMEM;
1149d1df6fd8SDavid Lebrun 
1150d1df6fd8SDavid Lebrun 	slwt = seg6_local_lwtunnel(newts);
1151d1df6fd8SDavid Lebrun 	slwt->action = nla_get_u32(tb[SEG6_LOCAL_ACTION]);
1152d1df6fd8SDavid Lebrun 
1153d1df6fd8SDavid Lebrun 	err = parse_nla_action(tb, slwt);
1154d1df6fd8SDavid Lebrun 	if (err < 0)
1155d1df6fd8SDavid Lebrun 		goto out_free;
1156d1df6fd8SDavid Lebrun 
1157d1df6fd8SDavid Lebrun 	newts->type = LWTUNNEL_ENCAP_SEG6_LOCAL;
1158d1df6fd8SDavid Lebrun 	newts->flags = LWTUNNEL_STATE_INPUT_REDIRECT;
1159d1df6fd8SDavid Lebrun 	newts->headroom = slwt->headroom;
1160d1df6fd8SDavid Lebrun 
1161d1df6fd8SDavid Lebrun 	*ts = newts;
1162d1df6fd8SDavid Lebrun 
1163d1df6fd8SDavid Lebrun 	return 0;
1164d1df6fd8SDavid Lebrun 
1165d1df6fd8SDavid Lebrun out_free:
1166d1df6fd8SDavid Lebrun 	kfree(newts);
1167d1df6fd8SDavid Lebrun 	return err;
1168d1df6fd8SDavid Lebrun }
1169d1df6fd8SDavid Lebrun 
1170d1df6fd8SDavid Lebrun static void seg6_local_destroy_state(struct lwtunnel_state *lwt)
1171d1df6fd8SDavid Lebrun {
1172d1df6fd8SDavid Lebrun 	struct seg6_local_lwt *slwt = seg6_local_lwtunnel(lwt);
1173d1df6fd8SDavid Lebrun 
1174964adce5SAndrea Mayer 	destroy_attrs(slwt);
1175004d4b27SMathieu Xhonneux 
1176004d4b27SMathieu Xhonneux 	return;
1177d1df6fd8SDavid Lebrun }
1178d1df6fd8SDavid Lebrun 
1179d1df6fd8SDavid Lebrun static int seg6_local_fill_encap(struct sk_buff *skb,
1180d1df6fd8SDavid Lebrun 				 struct lwtunnel_state *lwt)
1181d1df6fd8SDavid Lebrun {
1182d1df6fd8SDavid Lebrun 	struct seg6_local_lwt *slwt = seg6_local_lwtunnel(lwt);
1183d1df6fd8SDavid Lebrun 	struct seg6_action_param *param;
1184*0a3021f1SAndrea Mayer 	unsigned long attrs;
1185d1df6fd8SDavid Lebrun 	int i, err;
1186d1df6fd8SDavid Lebrun 
1187d1df6fd8SDavid Lebrun 	if (nla_put_u32(skb, SEG6_LOCAL_ACTION, slwt->action))
1188d1df6fd8SDavid Lebrun 		return -EMSGSIZE;
1189d1df6fd8SDavid Lebrun 
1190*0a3021f1SAndrea Mayer 	attrs = slwt->desc->attrs | slwt->parsed_optattrs;
1191*0a3021f1SAndrea Mayer 
1192d1df6fd8SDavid Lebrun 	for (i = 0; i < SEG6_LOCAL_MAX + 1; i++) {
1193*0a3021f1SAndrea Mayer 		if (attrs & (1 << i)) {
1194d1df6fd8SDavid Lebrun 			param = &seg6_action_params[i];
1195d1df6fd8SDavid Lebrun 			err = param->put(skb, slwt);
1196d1df6fd8SDavid Lebrun 			if (err < 0)
1197d1df6fd8SDavid Lebrun 				return err;
1198d1df6fd8SDavid Lebrun 		}
1199d1df6fd8SDavid Lebrun 	}
1200d1df6fd8SDavid Lebrun 
1201d1df6fd8SDavid Lebrun 	return 0;
1202d1df6fd8SDavid Lebrun }
1203d1df6fd8SDavid Lebrun 
1204d1df6fd8SDavid Lebrun static int seg6_local_get_encap_size(struct lwtunnel_state *lwt)
1205d1df6fd8SDavid Lebrun {
1206d1df6fd8SDavid Lebrun 	struct seg6_local_lwt *slwt = seg6_local_lwtunnel(lwt);
1207d1df6fd8SDavid Lebrun 	unsigned long attrs;
1208d1df6fd8SDavid Lebrun 	int nlsize;
1209d1df6fd8SDavid Lebrun 
1210d1df6fd8SDavid Lebrun 	nlsize = nla_total_size(4); /* action */
1211d1df6fd8SDavid Lebrun 
1212*0a3021f1SAndrea Mayer 	attrs = slwt->desc->attrs | slwt->parsed_optattrs;
1213d1df6fd8SDavid Lebrun 
1214d1df6fd8SDavid Lebrun 	if (attrs & (1 << SEG6_LOCAL_SRH))
1215d1df6fd8SDavid Lebrun 		nlsize += nla_total_size((slwt->srh->hdrlen + 1) << 3);
1216d1df6fd8SDavid Lebrun 
1217d1df6fd8SDavid Lebrun 	if (attrs & (1 << SEG6_LOCAL_TABLE))
1218d1df6fd8SDavid Lebrun 		nlsize += nla_total_size(4);
1219d1df6fd8SDavid Lebrun 
1220d1df6fd8SDavid Lebrun 	if (attrs & (1 << SEG6_LOCAL_NH4))
1221d1df6fd8SDavid Lebrun 		nlsize += nla_total_size(4);
1222d1df6fd8SDavid Lebrun 
1223d1df6fd8SDavid Lebrun 	if (attrs & (1 << SEG6_LOCAL_NH6))
1224d1df6fd8SDavid Lebrun 		nlsize += nla_total_size(16);
1225d1df6fd8SDavid Lebrun 
1226d1df6fd8SDavid Lebrun 	if (attrs & (1 << SEG6_LOCAL_IIF))
1227d1df6fd8SDavid Lebrun 		nlsize += nla_total_size(4);
1228d1df6fd8SDavid Lebrun 
1229d1df6fd8SDavid Lebrun 	if (attrs & (1 << SEG6_LOCAL_OIF))
1230d1df6fd8SDavid Lebrun 		nlsize += nla_total_size(4);
1231d1df6fd8SDavid Lebrun 
1232004d4b27SMathieu Xhonneux 	if (attrs & (1 << SEG6_LOCAL_BPF))
1233004d4b27SMathieu Xhonneux 		nlsize += nla_total_size(sizeof(struct nlattr)) +
1234004d4b27SMathieu Xhonneux 		       nla_total_size(MAX_PROG_NAME) +
1235004d4b27SMathieu Xhonneux 		       nla_total_size(4);
1236004d4b27SMathieu Xhonneux 
1237d1df6fd8SDavid Lebrun 	return nlsize;
1238d1df6fd8SDavid Lebrun }
1239d1df6fd8SDavid Lebrun 
1240d1df6fd8SDavid Lebrun static int seg6_local_cmp_encap(struct lwtunnel_state *a,
1241d1df6fd8SDavid Lebrun 				struct lwtunnel_state *b)
1242d1df6fd8SDavid Lebrun {
1243d1df6fd8SDavid Lebrun 	struct seg6_local_lwt *slwt_a, *slwt_b;
1244d1df6fd8SDavid Lebrun 	struct seg6_action_param *param;
1245*0a3021f1SAndrea Mayer 	unsigned long attrs_a, attrs_b;
1246d1df6fd8SDavid Lebrun 	int i;
1247d1df6fd8SDavid Lebrun 
1248d1df6fd8SDavid Lebrun 	slwt_a = seg6_local_lwtunnel(a);
1249d1df6fd8SDavid Lebrun 	slwt_b = seg6_local_lwtunnel(b);
1250d1df6fd8SDavid Lebrun 
1251d1df6fd8SDavid Lebrun 	if (slwt_a->action != slwt_b->action)
1252d1df6fd8SDavid Lebrun 		return 1;
1253d1df6fd8SDavid Lebrun 
1254*0a3021f1SAndrea Mayer 	attrs_a = slwt_a->desc->attrs | slwt_a->parsed_optattrs;
1255*0a3021f1SAndrea Mayer 	attrs_b = slwt_b->desc->attrs | slwt_b->parsed_optattrs;
1256*0a3021f1SAndrea Mayer 
1257*0a3021f1SAndrea Mayer 	if (attrs_a != attrs_b)
1258d1df6fd8SDavid Lebrun 		return 1;
1259d1df6fd8SDavid Lebrun 
1260d1df6fd8SDavid Lebrun 	for (i = 0; i < SEG6_LOCAL_MAX + 1; i++) {
1261*0a3021f1SAndrea Mayer 		if (attrs_a & (1 << i)) {
1262d1df6fd8SDavid Lebrun 			param = &seg6_action_params[i];
1263d1df6fd8SDavid Lebrun 			if (param->cmp(slwt_a, slwt_b))
1264d1df6fd8SDavid Lebrun 				return 1;
1265d1df6fd8SDavid Lebrun 		}
1266d1df6fd8SDavid Lebrun 	}
1267d1df6fd8SDavid Lebrun 
1268d1df6fd8SDavid Lebrun 	return 0;
1269d1df6fd8SDavid Lebrun }
1270d1df6fd8SDavid Lebrun 
1271d1df6fd8SDavid Lebrun static const struct lwtunnel_encap_ops seg6_local_ops = {
1272d1df6fd8SDavid Lebrun 	.build_state	= seg6_local_build_state,
1273d1df6fd8SDavid Lebrun 	.destroy_state	= seg6_local_destroy_state,
1274d1df6fd8SDavid Lebrun 	.input		= seg6_local_input,
1275d1df6fd8SDavid Lebrun 	.fill_encap	= seg6_local_fill_encap,
1276d1df6fd8SDavid Lebrun 	.get_encap_size	= seg6_local_get_encap_size,
1277d1df6fd8SDavid Lebrun 	.cmp_encap	= seg6_local_cmp_encap,
1278d1df6fd8SDavid Lebrun 	.owner		= THIS_MODULE,
1279d1df6fd8SDavid Lebrun };
1280d1df6fd8SDavid Lebrun 
1281d1df6fd8SDavid Lebrun int __init seg6_local_init(void)
1282d1df6fd8SDavid Lebrun {
1283d1df6fd8SDavid Lebrun 	return lwtunnel_encap_add_ops(&seg6_local_ops,
1284d1df6fd8SDavid Lebrun 				      LWTUNNEL_ENCAP_SEG6_LOCAL);
1285d1df6fd8SDavid Lebrun }
1286d1df6fd8SDavid Lebrun 
1287d1df6fd8SDavid Lebrun void seg6_local_exit(void)
1288d1df6fd8SDavid Lebrun {
1289d1df6fd8SDavid Lebrun 	lwtunnel_encap_del_ops(&seg6_local_ops, LWTUNNEL_ENCAP_SEG6_LOCAL);
1290d1df6fd8SDavid Lebrun }
1291