xref: /openbmc/linux/net/ipv6/seg6_local.c (revision fe94cc290f535709d3c5ebd1e472dfd0aec7ee79)
1d1df6fd8SDavid Lebrun /*
2d1df6fd8SDavid Lebrun  *  SR-IPv6 implementation
3d1df6fd8SDavid Lebrun  *
4d1df6fd8SDavid Lebrun  *  Author:
5d1df6fd8SDavid Lebrun  *  David Lebrun <david.lebrun@uclouvain.be>
6d1df6fd8SDavid Lebrun  *
7d1df6fd8SDavid Lebrun  *
8d1df6fd8SDavid Lebrun  *  This program is free software; you can redistribute it and/or
9d1df6fd8SDavid Lebrun  *        modify it under the terms of the GNU General Public License
10d1df6fd8SDavid Lebrun  *        as published by the Free Software Foundation; either version
11d1df6fd8SDavid Lebrun  *        2 of the License, or (at your option) any later version.
12d1df6fd8SDavid Lebrun  */
13d1df6fd8SDavid Lebrun 
14d1df6fd8SDavid Lebrun #include <linux/types.h>
15d1df6fd8SDavid Lebrun #include <linux/skbuff.h>
16d1df6fd8SDavid Lebrun #include <linux/net.h>
17d1df6fd8SDavid Lebrun #include <linux/module.h>
18d1df6fd8SDavid Lebrun #include <net/ip.h>
19d1df6fd8SDavid Lebrun #include <net/lwtunnel.h>
20d1df6fd8SDavid Lebrun #include <net/netevent.h>
21d1df6fd8SDavid Lebrun #include <net/netns/generic.h>
22d1df6fd8SDavid Lebrun #include <net/ip6_fib.h>
23d1df6fd8SDavid Lebrun #include <net/route.h>
24d1df6fd8SDavid Lebrun #include <net/seg6.h>
25d1df6fd8SDavid Lebrun #include <linux/seg6.h>
26d1df6fd8SDavid Lebrun #include <linux/seg6_local.h>
27d1df6fd8SDavid Lebrun #include <net/addrconf.h>
28d1df6fd8SDavid Lebrun #include <net/ip6_route.h>
29d1df6fd8SDavid Lebrun #include <net/dst_cache.h>
30d1df6fd8SDavid Lebrun #ifdef CONFIG_IPV6_SEG6_HMAC
31d1df6fd8SDavid Lebrun #include <net/seg6_hmac.h>
32d1df6fd8SDavid Lebrun #endif
331c1e761eSMathieu Xhonneux #include <net/seg6_local.h>
34891ef8ddSDavid Lebrun #include <linux/etherdevice.h>
35d1df6fd8SDavid Lebrun 
36d1df6fd8SDavid Lebrun struct seg6_local_lwt;
37d1df6fd8SDavid Lebrun 
38d1df6fd8SDavid Lebrun struct seg6_action_desc {
39d1df6fd8SDavid Lebrun 	int action;
40d1df6fd8SDavid Lebrun 	unsigned long attrs;
41d1df6fd8SDavid Lebrun 	int (*input)(struct sk_buff *skb, struct seg6_local_lwt *slwt);
42d1df6fd8SDavid Lebrun 	int static_headroom;
43d1df6fd8SDavid Lebrun };
44d1df6fd8SDavid Lebrun 
45d1df6fd8SDavid Lebrun struct seg6_local_lwt {
46d1df6fd8SDavid Lebrun 	int action;
47d1df6fd8SDavid Lebrun 	struct ipv6_sr_hdr *srh;
48d1df6fd8SDavid Lebrun 	int table;
49d1df6fd8SDavid Lebrun 	struct in_addr nh4;
50d1df6fd8SDavid Lebrun 	struct in6_addr nh6;
51d1df6fd8SDavid Lebrun 	int iif;
52d1df6fd8SDavid Lebrun 	int oif;
53d1df6fd8SDavid Lebrun 
54d1df6fd8SDavid Lebrun 	int headroom;
55d1df6fd8SDavid Lebrun 	struct seg6_action_desc *desc;
56d1df6fd8SDavid Lebrun };
57d1df6fd8SDavid Lebrun 
58d1df6fd8SDavid Lebrun static struct seg6_local_lwt *seg6_local_lwtunnel(struct lwtunnel_state *lwt)
59d1df6fd8SDavid Lebrun {
60d1df6fd8SDavid Lebrun 	return (struct seg6_local_lwt *)lwt->data;
61d1df6fd8SDavid Lebrun }
62d1df6fd8SDavid Lebrun 
63140f04c3SDavid Lebrun static struct ipv6_sr_hdr *get_srh(struct sk_buff *skb)
64140f04c3SDavid Lebrun {
65140f04c3SDavid Lebrun 	struct ipv6_sr_hdr *srh;
665829d70bSAhmed Abdelsalam 	int len, srhoff = 0;
67140f04c3SDavid Lebrun 
685829d70bSAhmed Abdelsalam 	if (ipv6_find_hdr(skb, &srhoff, IPPROTO_ROUTING, NULL, NULL) < 0)
69140f04c3SDavid Lebrun 		return NULL;
70140f04c3SDavid Lebrun 
715829d70bSAhmed Abdelsalam 	if (!pskb_may_pull(skb, srhoff + sizeof(*srh)))
725829d70bSAhmed Abdelsalam 		return NULL;
735829d70bSAhmed Abdelsalam 
745829d70bSAhmed Abdelsalam 	srh = (struct ipv6_sr_hdr *)(skb->data + srhoff);
755829d70bSAhmed Abdelsalam 
76140f04c3SDavid Lebrun 	len = (srh->hdrlen + 1) << 3;
77140f04c3SDavid Lebrun 
785829d70bSAhmed Abdelsalam 	if (!pskb_may_pull(skb, srhoff + len))
79140f04c3SDavid Lebrun 		return NULL;
80140f04c3SDavid Lebrun 
81140f04c3SDavid Lebrun 	if (!seg6_validate_srh(srh, len))
82140f04c3SDavid Lebrun 		return NULL;
83140f04c3SDavid Lebrun 
84140f04c3SDavid Lebrun 	return srh;
85140f04c3SDavid Lebrun }
86140f04c3SDavid Lebrun 
87140f04c3SDavid Lebrun static struct ipv6_sr_hdr *get_and_validate_srh(struct sk_buff *skb)
88140f04c3SDavid Lebrun {
89140f04c3SDavid Lebrun 	struct ipv6_sr_hdr *srh;
90140f04c3SDavid Lebrun 
91140f04c3SDavid Lebrun 	srh = get_srh(skb);
92140f04c3SDavid Lebrun 	if (!srh)
93140f04c3SDavid Lebrun 		return NULL;
94140f04c3SDavid Lebrun 
95140f04c3SDavid Lebrun 	if (srh->segments_left == 0)
96140f04c3SDavid Lebrun 		return NULL;
97140f04c3SDavid Lebrun 
98140f04c3SDavid Lebrun #ifdef CONFIG_IPV6_SEG6_HMAC
99140f04c3SDavid Lebrun 	if (!seg6_hmac_validate_skb(skb))
100140f04c3SDavid Lebrun 		return NULL;
101140f04c3SDavid Lebrun #endif
102140f04c3SDavid Lebrun 
103140f04c3SDavid Lebrun 	return srh;
104140f04c3SDavid Lebrun }
105140f04c3SDavid Lebrun 
106d7a669ddSDavid Lebrun static bool decap_and_validate(struct sk_buff *skb, int proto)
107d7a669ddSDavid Lebrun {
108d7a669ddSDavid Lebrun 	struct ipv6_sr_hdr *srh;
109d7a669ddSDavid Lebrun 	unsigned int off = 0;
110d7a669ddSDavid Lebrun 
111d7a669ddSDavid Lebrun 	srh = get_srh(skb);
112d7a669ddSDavid Lebrun 	if (srh && srh->segments_left > 0)
113d7a669ddSDavid Lebrun 		return false;
114d7a669ddSDavid Lebrun 
115d7a669ddSDavid Lebrun #ifdef CONFIG_IPV6_SEG6_HMAC
116d7a669ddSDavid Lebrun 	if (srh && !seg6_hmac_validate_skb(skb))
117d7a669ddSDavid Lebrun 		return false;
118d7a669ddSDavid Lebrun #endif
119d7a669ddSDavid Lebrun 
120d7a669ddSDavid Lebrun 	if (ipv6_find_hdr(skb, &off, proto, NULL, NULL) < 0)
121d7a669ddSDavid Lebrun 		return false;
122d7a669ddSDavid Lebrun 
123d7a669ddSDavid Lebrun 	if (!pskb_pull(skb, off))
124d7a669ddSDavid Lebrun 		return false;
125d7a669ddSDavid Lebrun 
126d7a669ddSDavid Lebrun 	skb_postpull_rcsum(skb, skb_network_header(skb), off);
127d7a669ddSDavid Lebrun 
128d7a669ddSDavid Lebrun 	skb_reset_network_header(skb);
129d7a669ddSDavid Lebrun 	skb_reset_transport_header(skb);
130d7a669ddSDavid Lebrun 	skb->encapsulation = 0;
131d7a669ddSDavid Lebrun 
132d7a669ddSDavid Lebrun 	return true;
133d7a669ddSDavid Lebrun }
134d7a669ddSDavid Lebrun 
135d7a669ddSDavid Lebrun static void advance_nextseg(struct ipv6_sr_hdr *srh, struct in6_addr *daddr)
136d7a669ddSDavid Lebrun {
137d7a669ddSDavid Lebrun 	struct in6_addr *addr;
138d7a669ddSDavid Lebrun 
139d7a669ddSDavid Lebrun 	srh->segments_left--;
140d7a669ddSDavid Lebrun 	addr = srh->segments + srh->segments_left;
141d7a669ddSDavid Lebrun 	*daddr = *addr;
142d7a669ddSDavid Lebrun }
143d7a669ddSDavid Lebrun 
1441c1e761eSMathieu Xhonneux int seg6_lookup_nexthop(struct sk_buff *skb, struct in6_addr *nhaddr,
145d7a669ddSDavid Lebrun 			u32 tbl_id)
146d7a669ddSDavid Lebrun {
147d7a669ddSDavid Lebrun 	struct net *net = dev_net(skb->dev);
148d7a669ddSDavid Lebrun 	struct ipv6hdr *hdr = ipv6_hdr(skb);
149d7a669ddSDavid Lebrun 	int flags = RT6_LOOKUP_F_HAS_SADDR;
150d7a669ddSDavid Lebrun 	struct dst_entry *dst = NULL;
151d7a669ddSDavid Lebrun 	struct rt6_info *rt;
152d7a669ddSDavid Lebrun 	struct flowi6 fl6;
153d7a669ddSDavid Lebrun 
154d7a669ddSDavid Lebrun 	fl6.flowi6_iif = skb->dev->ifindex;
155d7a669ddSDavid Lebrun 	fl6.daddr = nhaddr ? *nhaddr : hdr->daddr;
156d7a669ddSDavid Lebrun 	fl6.saddr = hdr->saddr;
157d7a669ddSDavid Lebrun 	fl6.flowlabel = ip6_flowinfo(hdr);
158d7a669ddSDavid Lebrun 	fl6.flowi6_mark = skb->mark;
159d7a669ddSDavid Lebrun 	fl6.flowi6_proto = hdr->nexthdr;
160d7a669ddSDavid Lebrun 
161d7a669ddSDavid Lebrun 	if (nhaddr)
162d7a669ddSDavid Lebrun 		fl6.flowi6_flags = FLOWI_FLAG_KNOWN_NH;
163d7a669ddSDavid Lebrun 
164d7a669ddSDavid Lebrun 	if (!tbl_id) {
165b75cc8f9SDavid Ahern 		dst = ip6_route_input_lookup(net, skb->dev, &fl6, skb, flags);
166d7a669ddSDavid Lebrun 	} else {
167d7a669ddSDavid Lebrun 		struct fib6_table *table;
168d7a669ddSDavid Lebrun 
169d7a669ddSDavid Lebrun 		table = fib6_get_table(net, tbl_id);
170d7a669ddSDavid Lebrun 		if (!table)
171d7a669ddSDavid Lebrun 			goto out;
172d7a669ddSDavid Lebrun 
173b75cc8f9SDavid Ahern 		rt = ip6_pol_route(net, table, 0, &fl6, skb, flags);
174d7a669ddSDavid Lebrun 		dst = &rt->dst;
175d7a669ddSDavid Lebrun 	}
176d7a669ddSDavid Lebrun 
177d7a669ddSDavid Lebrun 	if (dst && dst->dev->flags & IFF_LOOPBACK && !dst->error) {
178d7a669ddSDavid Lebrun 		dst_release(dst);
179d7a669ddSDavid Lebrun 		dst = NULL;
180d7a669ddSDavid Lebrun 	}
181d7a669ddSDavid Lebrun 
182d7a669ddSDavid Lebrun out:
183d7a669ddSDavid Lebrun 	if (!dst) {
184d7a669ddSDavid Lebrun 		rt = net->ipv6.ip6_blk_hole_entry;
185d7a669ddSDavid Lebrun 		dst = &rt->dst;
186d7a669ddSDavid Lebrun 		dst_hold(dst);
187d7a669ddSDavid Lebrun 	}
188d7a669ddSDavid Lebrun 
189d7a669ddSDavid Lebrun 	skb_dst_drop(skb);
190d7a669ddSDavid Lebrun 	skb_dst_set(skb, dst);
1911c1e761eSMathieu Xhonneux 	return dst->error;
192d7a669ddSDavid Lebrun }
193d7a669ddSDavid Lebrun 
194140f04c3SDavid Lebrun /* regular endpoint function */
195140f04c3SDavid Lebrun static int input_action_end(struct sk_buff *skb, struct seg6_local_lwt *slwt)
196140f04c3SDavid Lebrun {
197140f04c3SDavid Lebrun 	struct ipv6_sr_hdr *srh;
198140f04c3SDavid Lebrun 
199140f04c3SDavid Lebrun 	srh = get_and_validate_srh(skb);
200140f04c3SDavid Lebrun 	if (!srh)
201140f04c3SDavid Lebrun 		goto drop;
202140f04c3SDavid Lebrun 
203d7a669ddSDavid Lebrun 	advance_nextseg(srh, &ipv6_hdr(skb)->daddr);
204140f04c3SDavid Lebrun 
2051c1e761eSMathieu Xhonneux 	seg6_lookup_nexthop(skb, NULL, 0);
206140f04c3SDavid Lebrun 
207140f04c3SDavid Lebrun 	return dst_input(skb);
208140f04c3SDavid Lebrun 
209140f04c3SDavid Lebrun drop:
210140f04c3SDavid Lebrun 	kfree_skb(skb);
211140f04c3SDavid Lebrun 	return -EINVAL;
212140f04c3SDavid Lebrun }
213140f04c3SDavid Lebrun 
214140f04c3SDavid Lebrun /* regular endpoint, and forward to specified nexthop */
215140f04c3SDavid Lebrun static int input_action_end_x(struct sk_buff *skb, struct seg6_local_lwt *slwt)
216140f04c3SDavid Lebrun {
217140f04c3SDavid Lebrun 	struct ipv6_sr_hdr *srh;
218140f04c3SDavid Lebrun 
219140f04c3SDavid Lebrun 	srh = get_and_validate_srh(skb);
220140f04c3SDavid Lebrun 	if (!srh)
221140f04c3SDavid Lebrun 		goto drop;
222140f04c3SDavid Lebrun 
223d7a669ddSDavid Lebrun 	advance_nextseg(srh, &ipv6_hdr(skb)->daddr);
224140f04c3SDavid Lebrun 
2251c1e761eSMathieu Xhonneux 	seg6_lookup_nexthop(skb, &slwt->nh6, 0);
226140f04c3SDavid Lebrun 
227140f04c3SDavid Lebrun 	return dst_input(skb);
228140f04c3SDavid Lebrun 
229140f04c3SDavid Lebrun drop:
230140f04c3SDavid Lebrun 	kfree_skb(skb);
231140f04c3SDavid Lebrun 	return -EINVAL;
232140f04c3SDavid Lebrun }
233140f04c3SDavid Lebrun 
234891ef8ddSDavid Lebrun static int input_action_end_t(struct sk_buff *skb, struct seg6_local_lwt *slwt)
235891ef8ddSDavid Lebrun {
236891ef8ddSDavid Lebrun 	struct ipv6_sr_hdr *srh;
237891ef8ddSDavid Lebrun 
238891ef8ddSDavid Lebrun 	srh = get_and_validate_srh(skb);
239891ef8ddSDavid Lebrun 	if (!srh)
240891ef8ddSDavid Lebrun 		goto drop;
241891ef8ddSDavid Lebrun 
242891ef8ddSDavid Lebrun 	advance_nextseg(srh, &ipv6_hdr(skb)->daddr);
243891ef8ddSDavid Lebrun 
2441c1e761eSMathieu Xhonneux 	seg6_lookup_nexthop(skb, NULL, slwt->table);
245891ef8ddSDavid Lebrun 
246891ef8ddSDavid Lebrun 	return dst_input(skb);
247891ef8ddSDavid Lebrun 
248891ef8ddSDavid Lebrun drop:
249891ef8ddSDavid Lebrun 	kfree_skb(skb);
250891ef8ddSDavid Lebrun 	return -EINVAL;
251891ef8ddSDavid Lebrun }
252891ef8ddSDavid Lebrun 
253891ef8ddSDavid Lebrun /* decapsulate and forward inner L2 frame on specified interface */
254891ef8ddSDavid Lebrun static int input_action_end_dx2(struct sk_buff *skb,
255891ef8ddSDavid Lebrun 				struct seg6_local_lwt *slwt)
256891ef8ddSDavid Lebrun {
257891ef8ddSDavid Lebrun 	struct net *net = dev_net(skb->dev);
258891ef8ddSDavid Lebrun 	struct net_device *odev;
259891ef8ddSDavid Lebrun 	struct ethhdr *eth;
260891ef8ddSDavid Lebrun 
261891ef8ddSDavid Lebrun 	if (!decap_and_validate(skb, NEXTHDR_NONE))
262891ef8ddSDavid Lebrun 		goto drop;
263891ef8ddSDavid Lebrun 
264891ef8ddSDavid Lebrun 	if (!pskb_may_pull(skb, ETH_HLEN))
265891ef8ddSDavid Lebrun 		goto drop;
266891ef8ddSDavid Lebrun 
267891ef8ddSDavid Lebrun 	skb_reset_mac_header(skb);
268891ef8ddSDavid Lebrun 	eth = (struct ethhdr *)skb->data;
269891ef8ddSDavid Lebrun 
270891ef8ddSDavid Lebrun 	/* To determine the frame's protocol, we assume it is 802.3. This avoids
271891ef8ddSDavid Lebrun 	 * a call to eth_type_trans(), which is not really relevant for our
272891ef8ddSDavid Lebrun 	 * use case.
273891ef8ddSDavid Lebrun 	 */
274891ef8ddSDavid Lebrun 	if (!eth_proto_is_802_3(eth->h_proto))
275891ef8ddSDavid Lebrun 		goto drop;
276891ef8ddSDavid Lebrun 
277891ef8ddSDavid Lebrun 	odev = dev_get_by_index_rcu(net, slwt->oif);
278891ef8ddSDavid Lebrun 	if (!odev)
279891ef8ddSDavid Lebrun 		goto drop;
280891ef8ddSDavid Lebrun 
281891ef8ddSDavid Lebrun 	/* As we accept Ethernet frames, make sure the egress device is of
282891ef8ddSDavid Lebrun 	 * the correct type.
283891ef8ddSDavid Lebrun 	 */
284891ef8ddSDavid Lebrun 	if (odev->type != ARPHRD_ETHER)
285891ef8ddSDavid Lebrun 		goto drop;
286891ef8ddSDavid Lebrun 
287891ef8ddSDavid Lebrun 	if (!(odev->flags & IFF_UP) || !netif_carrier_ok(odev))
288891ef8ddSDavid Lebrun 		goto drop;
289891ef8ddSDavid Lebrun 
290891ef8ddSDavid Lebrun 	skb_orphan(skb);
291891ef8ddSDavid Lebrun 
292891ef8ddSDavid Lebrun 	if (skb_warn_if_lro(skb))
293891ef8ddSDavid Lebrun 		goto drop;
294891ef8ddSDavid Lebrun 
295891ef8ddSDavid Lebrun 	skb_forward_csum(skb);
296891ef8ddSDavid Lebrun 
297891ef8ddSDavid Lebrun 	if (skb->len - ETH_HLEN > odev->mtu)
298891ef8ddSDavid Lebrun 		goto drop;
299891ef8ddSDavid Lebrun 
300891ef8ddSDavid Lebrun 	skb->dev = odev;
301891ef8ddSDavid Lebrun 	skb->protocol = eth->h_proto;
302891ef8ddSDavid Lebrun 
303891ef8ddSDavid Lebrun 	return dev_queue_xmit(skb);
304891ef8ddSDavid Lebrun 
305891ef8ddSDavid Lebrun drop:
306891ef8ddSDavid Lebrun 	kfree_skb(skb);
307891ef8ddSDavid Lebrun 	return -EINVAL;
308891ef8ddSDavid Lebrun }
309891ef8ddSDavid Lebrun 
310140f04c3SDavid Lebrun /* decapsulate and forward to specified nexthop */
311140f04c3SDavid Lebrun static int input_action_end_dx6(struct sk_buff *skb,
312140f04c3SDavid Lebrun 				struct seg6_local_lwt *slwt)
313140f04c3SDavid Lebrun {
314d7a669ddSDavid Lebrun 	struct in6_addr *nhaddr = NULL;
315140f04c3SDavid Lebrun 
316140f04c3SDavid Lebrun 	/* this function accepts IPv6 encapsulated packets, with either
317140f04c3SDavid Lebrun 	 * an SRH with SL=0, or no SRH.
318140f04c3SDavid Lebrun 	 */
319140f04c3SDavid Lebrun 
320d7a669ddSDavid Lebrun 	if (!decap_and_validate(skb, IPPROTO_IPV6))
321140f04c3SDavid Lebrun 		goto drop;
322140f04c3SDavid Lebrun 
323d7a669ddSDavid Lebrun 	if (!pskb_may_pull(skb, sizeof(struct ipv6hdr)))
324140f04c3SDavid Lebrun 		goto drop;
325140f04c3SDavid Lebrun 
326140f04c3SDavid Lebrun 	/* The inner packet is not associated to any local interface,
327140f04c3SDavid Lebrun 	 * so we do not call netif_rx().
328140f04c3SDavid Lebrun 	 *
329140f04c3SDavid Lebrun 	 * If slwt->nh6 is set to ::, then lookup the nexthop for the
330140f04c3SDavid Lebrun 	 * inner packet's DA. Otherwise, use the specified nexthop.
331140f04c3SDavid Lebrun 	 */
332140f04c3SDavid Lebrun 
333d7a669ddSDavid Lebrun 	if (!ipv6_addr_any(&slwt->nh6))
334d7a669ddSDavid Lebrun 		nhaddr = &slwt->nh6;
335140f04c3SDavid Lebrun 
3361c1e761eSMathieu Xhonneux 	seg6_lookup_nexthop(skb, nhaddr, 0);
337140f04c3SDavid Lebrun 
338140f04c3SDavid Lebrun 	return dst_input(skb);
339140f04c3SDavid Lebrun drop:
340140f04c3SDavid Lebrun 	kfree_skb(skb);
341140f04c3SDavid Lebrun 	return -EINVAL;
342140f04c3SDavid Lebrun }
343140f04c3SDavid Lebrun 
344891ef8ddSDavid Lebrun static int input_action_end_dx4(struct sk_buff *skb,
345891ef8ddSDavid Lebrun 				struct seg6_local_lwt *slwt)
346891ef8ddSDavid Lebrun {
347891ef8ddSDavid Lebrun 	struct iphdr *iph;
348891ef8ddSDavid Lebrun 	__be32 nhaddr;
349891ef8ddSDavid Lebrun 	int err;
350891ef8ddSDavid Lebrun 
351891ef8ddSDavid Lebrun 	if (!decap_and_validate(skb, IPPROTO_IPIP))
352891ef8ddSDavid Lebrun 		goto drop;
353891ef8ddSDavid Lebrun 
354891ef8ddSDavid Lebrun 	if (!pskb_may_pull(skb, sizeof(struct iphdr)))
355891ef8ddSDavid Lebrun 		goto drop;
356891ef8ddSDavid Lebrun 
357891ef8ddSDavid Lebrun 	skb->protocol = htons(ETH_P_IP);
358891ef8ddSDavid Lebrun 
359891ef8ddSDavid Lebrun 	iph = ip_hdr(skb);
360891ef8ddSDavid Lebrun 
361891ef8ddSDavid Lebrun 	nhaddr = slwt->nh4.s_addr ?: iph->daddr;
362891ef8ddSDavid Lebrun 
363891ef8ddSDavid Lebrun 	skb_dst_drop(skb);
364891ef8ddSDavid Lebrun 
365891ef8ddSDavid Lebrun 	err = ip_route_input(skb, nhaddr, iph->saddr, 0, skb->dev);
366891ef8ddSDavid Lebrun 	if (err)
367891ef8ddSDavid Lebrun 		goto drop;
368891ef8ddSDavid Lebrun 
369891ef8ddSDavid Lebrun 	return dst_input(skb);
370891ef8ddSDavid Lebrun 
371891ef8ddSDavid Lebrun drop:
372891ef8ddSDavid Lebrun 	kfree_skb(skb);
373891ef8ddSDavid Lebrun 	return -EINVAL;
374891ef8ddSDavid Lebrun }
375891ef8ddSDavid Lebrun 
376891ef8ddSDavid Lebrun static int input_action_end_dt6(struct sk_buff *skb,
377891ef8ddSDavid Lebrun 				struct seg6_local_lwt *slwt)
378891ef8ddSDavid Lebrun {
379891ef8ddSDavid Lebrun 	if (!decap_and_validate(skb, IPPROTO_IPV6))
380891ef8ddSDavid Lebrun 		goto drop;
381891ef8ddSDavid Lebrun 
382891ef8ddSDavid Lebrun 	if (!pskb_may_pull(skb, sizeof(struct ipv6hdr)))
383891ef8ddSDavid Lebrun 		goto drop;
384891ef8ddSDavid Lebrun 
3851c1e761eSMathieu Xhonneux 	seg6_lookup_nexthop(skb, NULL, slwt->table);
386891ef8ddSDavid Lebrun 
387891ef8ddSDavid Lebrun 	return dst_input(skb);
388891ef8ddSDavid Lebrun 
389891ef8ddSDavid Lebrun drop:
390891ef8ddSDavid Lebrun 	kfree_skb(skb);
391891ef8ddSDavid Lebrun 	return -EINVAL;
392891ef8ddSDavid Lebrun }
393891ef8ddSDavid Lebrun 
394140f04c3SDavid Lebrun /* push an SRH on top of the current one */
395140f04c3SDavid Lebrun static int input_action_end_b6(struct sk_buff *skb, struct seg6_local_lwt *slwt)
396140f04c3SDavid Lebrun {
397140f04c3SDavid Lebrun 	struct ipv6_sr_hdr *srh;
398140f04c3SDavid Lebrun 	int err = -EINVAL;
399140f04c3SDavid Lebrun 
400140f04c3SDavid Lebrun 	srh = get_and_validate_srh(skb);
401140f04c3SDavid Lebrun 	if (!srh)
402140f04c3SDavid Lebrun 		goto drop;
403140f04c3SDavid Lebrun 
404140f04c3SDavid Lebrun 	err = seg6_do_srh_inline(skb, slwt->srh);
405140f04c3SDavid Lebrun 	if (err)
406140f04c3SDavid Lebrun 		goto drop;
407140f04c3SDavid Lebrun 
408140f04c3SDavid Lebrun 	ipv6_hdr(skb)->payload_len = htons(skb->len - sizeof(struct ipv6hdr));
409140f04c3SDavid Lebrun 	skb_set_transport_header(skb, sizeof(struct ipv6hdr));
410140f04c3SDavid Lebrun 
4111c1e761eSMathieu Xhonneux 	seg6_lookup_nexthop(skb, NULL, 0);
412140f04c3SDavid Lebrun 
413140f04c3SDavid Lebrun 	return dst_input(skb);
414140f04c3SDavid Lebrun 
415140f04c3SDavid Lebrun drop:
416140f04c3SDavid Lebrun 	kfree_skb(skb);
417140f04c3SDavid Lebrun 	return err;
418140f04c3SDavid Lebrun }
419140f04c3SDavid Lebrun 
420140f04c3SDavid Lebrun /* encapsulate within an outer IPv6 header and a specified SRH */
421140f04c3SDavid Lebrun static int input_action_end_b6_encap(struct sk_buff *skb,
422140f04c3SDavid Lebrun 				     struct seg6_local_lwt *slwt)
423140f04c3SDavid Lebrun {
424140f04c3SDavid Lebrun 	struct ipv6_sr_hdr *srh;
425140f04c3SDavid Lebrun 	int err = -EINVAL;
426140f04c3SDavid Lebrun 
427140f04c3SDavid Lebrun 	srh = get_and_validate_srh(skb);
428140f04c3SDavid Lebrun 	if (!srh)
429140f04c3SDavid Lebrun 		goto drop;
430140f04c3SDavid Lebrun 
431d7a669ddSDavid Lebrun 	advance_nextseg(srh, &ipv6_hdr(skb)->daddr);
432140f04c3SDavid Lebrun 
433140f04c3SDavid Lebrun 	skb_reset_inner_headers(skb);
434140f04c3SDavid Lebrun 	skb->encapsulation = 1;
435140f04c3SDavid Lebrun 
43632d99d0bSDavid Lebrun 	err = seg6_do_srh_encap(skb, slwt->srh, IPPROTO_IPV6);
437140f04c3SDavid Lebrun 	if (err)
438140f04c3SDavid Lebrun 		goto drop;
439140f04c3SDavid Lebrun 
440140f04c3SDavid Lebrun 	ipv6_hdr(skb)->payload_len = htons(skb->len - sizeof(struct ipv6hdr));
441140f04c3SDavid Lebrun 	skb_set_transport_header(skb, sizeof(struct ipv6hdr));
442140f04c3SDavid Lebrun 
4431c1e761eSMathieu Xhonneux 	seg6_lookup_nexthop(skb, NULL, 0);
444140f04c3SDavid Lebrun 
445140f04c3SDavid Lebrun 	return dst_input(skb);
446140f04c3SDavid Lebrun 
447140f04c3SDavid Lebrun drop:
448140f04c3SDavid Lebrun 	kfree_skb(skb);
449140f04c3SDavid Lebrun 	return err;
450140f04c3SDavid Lebrun }
451140f04c3SDavid Lebrun 
452*fe94cc29SMathieu Xhonneux DEFINE_PER_CPU(struct seg6_bpf_srh_state, seg6_bpf_srh_states);
453*fe94cc29SMathieu Xhonneux 
454d1df6fd8SDavid Lebrun static struct seg6_action_desc seg6_action_table[] = {
455d1df6fd8SDavid Lebrun 	{
456d1df6fd8SDavid Lebrun 		.action		= SEG6_LOCAL_ACTION_END,
457d1df6fd8SDavid Lebrun 		.attrs		= 0,
458140f04c3SDavid Lebrun 		.input		= input_action_end,
459d1df6fd8SDavid Lebrun 	},
460140f04c3SDavid Lebrun 	{
461140f04c3SDavid Lebrun 		.action		= SEG6_LOCAL_ACTION_END_X,
462140f04c3SDavid Lebrun 		.attrs		= (1 << SEG6_LOCAL_NH6),
463140f04c3SDavid Lebrun 		.input		= input_action_end_x,
464140f04c3SDavid Lebrun 	},
465140f04c3SDavid Lebrun 	{
466891ef8ddSDavid Lebrun 		.action		= SEG6_LOCAL_ACTION_END_T,
467891ef8ddSDavid Lebrun 		.attrs		= (1 << SEG6_LOCAL_TABLE),
468891ef8ddSDavid Lebrun 		.input		= input_action_end_t,
469891ef8ddSDavid Lebrun 	},
470891ef8ddSDavid Lebrun 	{
471891ef8ddSDavid Lebrun 		.action		= SEG6_LOCAL_ACTION_END_DX2,
472891ef8ddSDavid Lebrun 		.attrs		= (1 << SEG6_LOCAL_OIF),
473891ef8ddSDavid Lebrun 		.input		= input_action_end_dx2,
474891ef8ddSDavid Lebrun 	},
475891ef8ddSDavid Lebrun 	{
476140f04c3SDavid Lebrun 		.action		= SEG6_LOCAL_ACTION_END_DX6,
477140f04c3SDavid Lebrun 		.attrs		= (1 << SEG6_LOCAL_NH6),
478140f04c3SDavid Lebrun 		.input		= input_action_end_dx6,
479140f04c3SDavid Lebrun 	},
480140f04c3SDavid Lebrun 	{
481891ef8ddSDavid Lebrun 		.action		= SEG6_LOCAL_ACTION_END_DX4,
482891ef8ddSDavid Lebrun 		.attrs		= (1 << SEG6_LOCAL_NH4),
483891ef8ddSDavid Lebrun 		.input		= input_action_end_dx4,
484891ef8ddSDavid Lebrun 	},
485891ef8ddSDavid Lebrun 	{
486891ef8ddSDavid Lebrun 		.action		= SEG6_LOCAL_ACTION_END_DT6,
487891ef8ddSDavid Lebrun 		.attrs		= (1 << SEG6_LOCAL_TABLE),
488891ef8ddSDavid Lebrun 		.input		= input_action_end_dt6,
489891ef8ddSDavid Lebrun 	},
490891ef8ddSDavid Lebrun 	{
491140f04c3SDavid Lebrun 		.action		= SEG6_LOCAL_ACTION_END_B6,
492140f04c3SDavid Lebrun 		.attrs		= (1 << SEG6_LOCAL_SRH),
493140f04c3SDavid Lebrun 		.input		= input_action_end_b6,
494140f04c3SDavid Lebrun 	},
495140f04c3SDavid Lebrun 	{
496140f04c3SDavid Lebrun 		.action		= SEG6_LOCAL_ACTION_END_B6_ENCAP,
497140f04c3SDavid Lebrun 		.attrs		= (1 << SEG6_LOCAL_SRH),
498140f04c3SDavid Lebrun 		.input		= input_action_end_b6_encap,
499140f04c3SDavid Lebrun 		.static_headroom	= sizeof(struct ipv6hdr),
500140f04c3SDavid Lebrun 	}
501d1df6fd8SDavid Lebrun };
502d1df6fd8SDavid Lebrun 
503d1df6fd8SDavid Lebrun static struct seg6_action_desc *__get_action_desc(int action)
504d1df6fd8SDavid Lebrun {
505d1df6fd8SDavid Lebrun 	struct seg6_action_desc *desc;
506d1df6fd8SDavid Lebrun 	int i, count;
507d1df6fd8SDavid Lebrun 
508709af180SColin Ian King 	count = ARRAY_SIZE(seg6_action_table);
509d1df6fd8SDavid Lebrun 	for (i = 0; i < count; i++) {
510d1df6fd8SDavid Lebrun 		desc = &seg6_action_table[i];
511d1df6fd8SDavid Lebrun 		if (desc->action == action)
512d1df6fd8SDavid Lebrun 			return desc;
513d1df6fd8SDavid Lebrun 	}
514d1df6fd8SDavid Lebrun 
515d1df6fd8SDavid Lebrun 	return NULL;
516d1df6fd8SDavid Lebrun }
517d1df6fd8SDavid Lebrun 
518d1df6fd8SDavid Lebrun static int seg6_local_input(struct sk_buff *skb)
519d1df6fd8SDavid Lebrun {
520d1df6fd8SDavid Lebrun 	struct dst_entry *orig_dst = skb_dst(skb);
521d1df6fd8SDavid Lebrun 	struct seg6_action_desc *desc;
522d1df6fd8SDavid Lebrun 	struct seg6_local_lwt *slwt;
523d1df6fd8SDavid Lebrun 
5246285217fSDavid Lebrun 	if (skb->protocol != htons(ETH_P_IPV6)) {
5256285217fSDavid Lebrun 		kfree_skb(skb);
5266285217fSDavid Lebrun 		return -EINVAL;
5276285217fSDavid Lebrun 	}
5286285217fSDavid Lebrun 
529d1df6fd8SDavid Lebrun 	slwt = seg6_local_lwtunnel(orig_dst->lwtstate);
530d1df6fd8SDavid Lebrun 	desc = slwt->desc;
531d1df6fd8SDavid Lebrun 
532d1df6fd8SDavid Lebrun 	return desc->input(skb, slwt);
533d1df6fd8SDavid Lebrun }
534d1df6fd8SDavid Lebrun 
535d1df6fd8SDavid Lebrun static const struct nla_policy seg6_local_policy[SEG6_LOCAL_MAX + 1] = {
536d1df6fd8SDavid Lebrun 	[SEG6_LOCAL_ACTION]	= { .type = NLA_U32 },
537d1df6fd8SDavid Lebrun 	[SEG6_LOCAL_SRH]	= { .type = NLA_BINARY },
538d1df6fd8SDavid Lebrun 	[SEG6_LOCAL_TABLE]	= { .type = NLA_U32 },
539d1df6fd8SDavid Lebrun 	[SEG6_LOCAL_NH4]	= { .type = NLA_BINARY,
540d1df6fd8SDavid Lebrun 				    .len = sizeof(struct in_addr) },
541d1df6fd8SDavid Lebrun 	[SEG6_LOCAL_NH6]	= { .type = NLA_BINARY,
542d1df6fd8SDavid Lebrun 				    .len = sizeof(struct in6_addr) },
543d1df6fd8SDavid Lebrun 	[SEG6_LOCAL_IIF]	= { .type = NLA_U32 },
544d1df6fd8SDavid Lebrun 	[SEG6_LOCAL_OIF]	= { .type = NLA_U32 },
545d1df6fd8SDavid Lebrun };
546d1df6fd8SDavid Lebrun 
5472d9cc60aSDavid Lebrun static int parse_nla_srh(struct nlattr **attrs, struct seg6_local_lwt *slwt)
5482d9cc60aSDavid Lebrun {
5492d9cc60aSDavid Lebrun 	struct ipv6_sr_hdr *srh;
5502d9cc60aSDavid Lebrun 	int len;
5512d9cc60aSDavid Lebrun 
5522d9cc60aSDavid Lebrun 	srh = nla_data(attrs[SEG6_LOCAL_SRH]);
5532d9cc60aSDavid Lebrun 	len = nla_len(attrs[SEG6_LOCAL_SRH]);
5542d9cc60aSDavid Lebrun 
5552d9cc60aSDavid Lebrun 	/* SRH must contain at least one segment */
5562d9cc60aSDavid Lebrun 	if (len < sizeof(*srh) + sizeof(struct in6_addr))
5572d9cc60aSDavid Lebrun 		return -EINVAL;
5582d9cc60aSDavid Lebrun 
5592d9cc60aSDavid Lebrun 	if (!seg6_validate_srh(srh, len))
5602d9cc60aSDavid Lebrun 		return -EINVAL;
5612d9cc60aSDavid Lebrun 
5622d9cc60aSDavid Lebrun 	slwt->srh = kmalloc(len, GFP_KERNEL);
5632d9cc60aSDavid Lebrun 	if (!slwt->srh)
5642d9cc60aSDavid Lebrun 		return -ENOMEM;
5652d9cc60aSDavid Lebrun 
5662d9cc60aSDavid Lebrun 	memcpy(slwt->srh, srh, len);
5672d9cc60aSDavid Lebrun 
5682d9cc60aSDavid Lebrun 	slwt->headroom += len;
5692d9cc60aSDavid Lebrun 
5702d9cc60aSDavid Lebrun 	return 0;
5712d9cc60aSDavid Lebrun }
5722d9cc60aSDavid Lebrun 
5732d9cc60aSDavid Lebrun static int put_nla_srh(struct sk_buff *skb, struct seg6_local_lwt *slwt)
5742d9cc60aSDavid Lebrun {
5752d9cc60aSDavid Lebrun 	struct ipv6_sr_hdr *srh;
5762d9cc60aSDavid Lebrun 	struct nlattr *nla;
5772d9cc60aSDavid Lebrun 	int len;
5782d9cc60aSDavid Lebrun 
5792d9cc60aSDavid Lebrun 	srh = slwt->srh;
5802d9cc60aSDavid Lebrun 	len = (srh->hdrlen + 1) << 3;
5812d9cc60aSDavid Lebrun 
5822d9cc60aSDavid Lebrun 	nla = nla_reserve(skb, SEG6_LOCAL_SRH, len);
5832d9cc60aSDavid Lebrun 	if (!nla)
5842d9cc60aSDavid Lebrun 		return -EMSGSIZE;
5852d9cc60aSDavid Lebrun 
5862d9cc60aSDavid Lebrun 	memcpy(nla_data(nla), srh, len);
5872d9cc60aSDavid Lebrun 
5882d9cc60aSDavid Lebrun 	return 0;
5892d9cc60aSDavid Lebrun }
5902d9cc60aSDavid Lebrun 
5912d9cc60aSDavid Lebrun static int cmp_nla_srh(struct seg6_local_lwt *a, struct seg6_local_lwt *b)
5922d9cc60aSDavid Lebrun {
5932d9cc60aSDavid Lebrun 	int len = (a->srh->hdrlen + 1) << 3;
5942d9cc60aSDavid Lebrun 
5952d9cc60aSDavid Lebrun 	if (len != ((b->srh->hdrlen + 1) << 3))
5962d9cc60aSDavid Lebrun 		return 1;
5972d9cc60aSDavid Lebrun 
5982d9cc60aSDavid Lebrun 	return memcmp(a->srh, b->srh, len);
5992d9cc60aSDavid Lebrun }
6002d9cc60aSDavid Lebrun 
6012d9cc60aSDavid Lebrun static int parse_nla_table(struct nlattr **attrs, struct seg6_local_lwt *slwt)
6022d9cc60aSDavid Lebrun {
6032d9cc60aSDavid Lebrun 	slwt->table = nla_get_u32(attrs[SEG6_LOCAL_TABLE]);
6042d9cc60aSDavid Lebrun 
6052d9cc60aSDavid Lebrun 	return 0;
6062d9cc60aSDavid Lebrun }
6072d9cc60aSDavid Lebrun 
6082d9cc60aSDavid Lebrun static int put_nla_table(struct sk_buff *skb, struct seg6_local_lwt *slwt)
6092d9cc60aSDavid Lebrun {
6102d9cc60aSDavid Lebrun 	if (nla_put_u32(skb, SEG6_LOCAL_TABLE, slwt->table))
6112d9cc60aSDavid Lebrun 		return -EMSGSIZE;
6122d9cc60aSDavid Lebrun 
6132d9cc60aSDavid Lebrun 	return 0;
6142d9cc60aSDavid Lebrun }
6152d9cc60aSDavid Lebrun 
6162d9cc60aSDavid Lebrun static int cmp_nla_table(struct seg6_local_lwt *a, struct seg6_local_lwt *b)
6172d9cc60aSDavid Lebrun {
6182d9cc60aSDavid Lebrun 	if (a->table != b->table)
6192d9cc60aSDavid Lebrun 		return 1;
6202d9cc60aSDavid Lebrun 
6212d9cc60aSDavid Lebrun 	return 0;
6222d9cc60aSDavid Lebrun }
6232d9cc60aSDavid Lebrun 
6242d9cc60aSDavid Lebrun static int parse_nla_nh4(struct nlattr **attrs, struct seg6_local_lwt *slwt)
6252d9cc60aSDavid Lebrun {
6262d9cc60aSDavid Lebrun 	memcpy(&slwt->nh4, nla_data(attrs[SEG6_LOCAL_NH4]),
6272d9cc60aSDavid Lebrun 	       sizeof(struct in_addr));
6282d9cc60aSDavid Lebrun 
6292d9cc60aSDavid Lebrun 	return 0;
6302d9cc60aSDavid Lebrun }
6312d9cc60aSDavid Lebrun 
6322d9cc60aSDavid Lebrun static int put_nla_nh4(struct sk_buff *skb, struct seg6_local_lwt *slwt)
6332d9cc60aSDavid Lebrun {
6342d9cc60aSDavid Lebrun 	struct nlattr *nla;
6352d9cc60aSDavid Lebrun 
6362d9cc60aSDavid Lebrun 	nla = nla_reserve(skb, SEG6_LOCAL_NH4, sizeof(struct in_addr));
6372d9cc60aSDavid Lebrun 	if (!nla)
6382d9cc60aSDavid Lebrun 		return -EMSGSIZE;
6392d9cc60aSDavid Lebrun 
6402d9cc60aSDavid Lebrun 	memcpy(nla_data(nla), &slwt->nh4, sizeof(struct in_addr));
6412d9cc60aSDavid Lebrun 
6422d9cc60aSDavid Lebrun 	return 0;
6432d9cc60aSDavid Lebrun }
6442d9cc60aSDavid Lebrun 
6452d9cc60aSDavid Lebrun static int cmp_nla_nh4(struct seg6_local_lwt *a, struct seg6_local_lwt *b)
6462d9cc60aSDavid Lebrun {
6472d9cc60aSDavid Lebrun 	return memcmp(&a->nh4, &b->nh4, sizeof(struct in_addr));
6482d9cc60aSDavid Lebrun }
6492d9cc60aSDavid Lebrun 
6502d9cc60aSDavid Lebrun static int parse_nla_nh6(struct nlattr **attrs, struct seg6_local_lwt *slwt)
6512d9cc60aSDavid Lebrun {
6522d9cc60aSDavid Lebrun 	memcpy(&slwt->nh6, nla_data(attrs[SEG6_LOCAL_NH6]),
6532d9cc60aSDavid Lebrun 	       sizeof(struct in6_addr));
6542d9cc60aSDavid Lebrun 
6552d9cc60aSDavid Lebrun 	return 0;
6562d9cc60aSDavid Lebrun }
6572d9cc60aSDavid Lebrun 
6582d9cc60aSDavid Lebrun static int put_nla_nh6(struct sk_buff *skb, struct seg6_local_lwt *slwt)
6592d9cc60aSDavid Lebrun {
6602d9cc60aSDavid Lebrun 	struct nlattr *nla;
6612d9cc60aSDavid Lebrun 
6622d9cc60aSDavid Lebrun 	nla = nla_reserve(skb, SEG6_LOCAL_NH6, sizeof(struct in6_addr));
6632d9cc60aSDavid Lebrun 	if (!nla)
6642d9cc60aSDavid Lebrun 		return -EMSGSIZE;
6652d9cc60aSDavid Lebrun 
6662d9cc60aSDavid Lebrun 	memcpy(nla_data(nla), &slwt->nh6, sizeof(struct in6_addr));
6672d9cc60aSDavid Lebrun 
6682d9cc60aSDavid Lebrun 	return 0;
6692d9cc60aSDavid Lebrun }
6702d9cc60aSDavid Lebrun 
6712d9cc60aSDavid Lebrun static int cmp_nla_nh6(struct seg6_local_lwt *a, struct seg6_local_lwt *b)
6722d9cc60aSDavid Lebrun {
6732d9cc60aSDavid Lebrun 	return memcmp(&a->nh6, &b->nh6, sizeof(struct in6_addr));
6742d9cc60aSDavid Lebrun }
6752d9cc60aSDavid Lebrun 
6762d9cc60aSDavid Lebrun static int parse_nla_iif(struct nlattr **attrs, struct seg6_local_lwt *slwt)
6772d9cc60aSDavid Lebrun {
6782d9cc60aSDavid Lebrun 	slwt->iif = nla_get_u32(attrs[SEG6_LOCAL_IIF]);
6792d9cc60aSDavid Lebrun 
6802d9cc60aSDavid Lebrun 	return 0;
6812d9cc60aSDavid Lebrun }
6822d9cc60aSDavid Lebrun 
6832d9cc60aSDavid Lebrun static int put_nla_iif(struct sk_buff *skb, struct seg6_local_lwt *slwt)
6842d9cc60aSDavid Lebrun {
6852d9cc60aSDavid Lebrun 	if (nla_put_u32(skb, SEG6_LOCAL_IIF, slwt->iif))
6862d9cc60aSDavid Lebrun 		return -EMSGSIZE;
6872d9cc60aSDavid Lebrun 
6882d9cc60aSDavid Lebrun 	return 0;
6892d9cc60aSDavid Lebrun }
6902d9cc60aSDavid Lebrun 
6912d9cc60aSDavid Lebrun static int cmp_nla_iif(struct seg6_local_lwt *a, struct seg6_local_lwt *b)
6922d9cc60aSDavid Lebrun {
6932d9cc60aSDavid Lebrun 	if (a->iif != b->iif)
6942d9cc60aSDavid Lebrun 		return 1;
6952d9cc60aSDavid Lebrun 
6962d9cc60aSDavid Lebrun 	return 0;
6972d9cc60aSDavid Lebrun }
6982d9cc60aSDavid Lebrun 
6992d9cc60aSDavid Lebrun static int parse_nla_oif(struct nlattr **attrs, struct seg6_local_lwt *slwt)
7002d9cc60aSDavid Lebrun {
7012d9cc60aSDavid Lebrun 	slwt->oif = nla_get_u32(attrs[SEG6_LOCAL_OIF]);
7022d9cc60aSDavid Lebrun 
7032d9cc60aSDavid Lebrun 	return 0;
7042d9cc60aSDavid Lebrun }
7052d9cc60aSDavid Lebrun 
7062d9cc60aSDavid Lebrun static int put_nla_oif(struct sk_buff *skb, struct seg6_local_lwt *slwt)
7072d9cc60aSDavid Lebrun {
7082d9cc60aSDavid Lebrun 	if (nla_put_u32(skb, SEG6_LOCAL_OIF, slwt->oif))
7092d9cc60aSDavid Lebrun 		return -EMSGSIZE;
7102d9cc60aSDavid Lebrun 
7112d9cc60aSDavid Lebrun 	return 0;
7122d9cc60aSDavid Lebrun }
7132d9cc60aSDavid Lebrun 
7142d9cc60aSDavid Lebrun static int cmp_nla_oif(struct seg6_local_lwt *a, struct seg6_local_lwt *b)
7152d9cc60aSDavid Lebrun {
7162d9cc60aSDavid Lebrun 	if (a->oif != b->oif)
7172d9cc60aSDavid Lebrun 		return 1;
7182d9cc60aSDavid Lebrun 
7192d9cc60aSDavid Lebrun 	return 0;
7202d9cc60aSDavid Lebrun }
7212d9cc60aSDavid Lebrun 
722d1df6fd8SDavid Lebrun struct seg6_action_param {
723d1df6fd8SDavid Lebrun 	int (*parse)(struct nlattr **attrs, struct seg6_local_lwt *slwt);
724d1df6fd8SDavid Lebrun 	int (*put)(struct sk_buff *skb, struct seg6_local_lwt *slwt);
725d1df6fd8SDavid Lebrun 	int (*cmp)(struct seg6_local_lwt *a, struct seg6_local_lwt *b);
726d1df6fd8SDavid Lebrun };
727d1df6fd8SDavid Lebrun 
728d1df6fd8SDavid Lebrun static struct seg6_action_param seg6_action_params[SEG6_LOCAL_MAX + 1] = {
7292d9cc60aSDavid Lebrun 	[SEG6_LOCAL_SRH]	= { .parse = parse_nla_srh,
7302d9cc60aSDavid Lebrun 				    .put = put_nla_srh,
7312d9cc60aSDavid Lebrun 				    .cmp = cmp_nla_srh },
732d1df6fd8SDavid Lebrun 
7332d9cc60aSDavid Lebrun 	[SEG6_LOCAL_TABLE]	= { .parse = parse_nla_table,
7342d9cc60aSDavid Lebrun 				    .put = put_nla_table,
7352d9cc60aSDavid Lebrun 				    .cmp = cmp_nla_table },
736d1df6fd8SDavid Lebrun 
7372d9cc60aSDavid Lebrun 	[SEG6_LOCAL_NH4]	= { .parse = parse_nla_nh4,
7382d9cc60aSDavid Lebrun 				    .put = put_nla_nh4,
7392d9cc60aSDavid Lebrun 				    .cmp = cmp_nla_nh4 },
740d1df6fd8SDavid Lebrun 
7412d9cc60aSDavid Lebrun 	[SEG6_LOCAL_NH6]	= { .parse = parse_nla_nh6,
7422d9cc60aSDavid Lebrun 				    .put = put_nla_nh6,
7432d9cc60aSDavid Lebrun 				    .cmp = cmp_nla_nh6 },
744d1df6fd8SDavid Lebrun 
7452d9cc60aSDavid Lebrun 	[SEG6_LOCAL_IIF]	= { .parse = parse_nla_iif,
7462d9cc60aSDavid Lebrun 				    .put = put_nla_iif,
7472d9cc60aSDavid Lebrun 				    .cmp = cmp_nla_iif },
748d1df6fd8SDavid Lebrun 
7492d9cc60aSDavid Lebrun 	[SEG6_LOCAL_OIF]	= { .parse = parse_nla_oif,
7502d9cc60aSDavid Lebrun 				    .put = put_nla_oif,
7512d9cc60aSDavid Lebrun 				    .cmp = cmp_nla_oif },
752d1df6fd8SDavid Lebrun };
753d1df6fd8SDavid Lebrun 
754d1df6fd8SDavid Lebrun static int parse_nla_action(struct nlattr **attrs, struct seg6_local_lwt *slwt)
755d1df6fd8SDavid Lebrun {
756d1df6fd8SDavid Lebrun 	struct seg6_action_param *param;
757d1df6fd8SDavid Lebrun 	struct seg6_action_desc *desc;
758d1df6fd8SDavid Lebrun 	int i, err;
759d1df6fd8SDavid Lebrun 
760d1df6fd8SDavid Lebrun 	desc = __get_action_desc(slwt->action);
761d1df6fd8SDavid Lebrun 	if (!desc)
762d1df6fd8SDavid Lebrun 		return -EINVAL;
763d1df6fd8SDavid Lebrun 
764d1df6fd8SDavid Lebrun 	if (!desc->input)
765d1df6fd8SDavid Lebrun 		return -EOPNOTSUPP;
766d1df6fd8SDavid Lebrun 
767d1df6fd8SDavid Lebrun 	slwt->desc = desc;
768d1df6fd8SDavid Lebrun 	slwt->headroom += desc->static_headroom;
769d1df6fd8SDavid Lebrun 
770d1df6fd8SDavid Lebrun 	for (i = 0; i < SEG6_LOCAL_MAX + 1; i++) {
771d1df6fd8SDavid Lebrun 		if (desc->attrs & (1 << i)) {
772d1df6fd8SDavid Lebrun 			if (!attrs[i])
773d1df6fd8SDavid Lebrun 				return -EINVAL;
774d1df6fd8SDavid Lebrun 
775d1df6fd8SDavid Lebrun 			param = &seg6_action_params[i];
776d1df6fd8SDavid Lebrun 
777d1df6fd8SDavid Lebrun 			err = param->parse(attrs, slwt);
778d1df6fd8SDavid Lebrun 			if (err < 0)
779d1df6fd8SDavid Lebrun 				return err;
780d1df6fd8SDavid Lebrun 		}
781d1df6fd8SDavid Lebrun 	}
782d1df6fd8SDavid Lebrun 
783d1df6fd8SDavid Lebrun 	return 0;
784d1df6fd8SDavid Lebrun }
785d1df6fd8SDavid Lebrun 
786d1df6fd8SDavid Lebrun static int seg6_local_build_state(struct nlattr *nla, unsigned int family,
787d1df6fd8SDavid Lebrun 				  const void *cfg, struct lwtunnel_state **ts,
788d1df6fd8SDavid Lebrun 				  struct netlink_ext_ack *extack)
789d1df6fd8SDavid Lebrun {
790d1df6fd8SDavid Lebrun 	struct nlattr *tb[SEG6_LOCAL_MAX + 1];
791d1df6fd8SDavid Lebrun 	struct lwtunnel_state *newts;
792d1df6fd8SDavid Lebrun 	struct seg6_local_lwt *slwt;
793d1df6fd8SDavid Lebrun 	int err;
794d1df6fd8SDavid Lebrun 
7956285217fSDavid Lebrun 	if (family != AF_INET6)
7966285217fSDavid Lebrun 		return -EINVAL;
7976285217fSDavid Lebrun 
798d1df6fd8SDavid Lebrun 	err = nla_parse_nested(tb, SEG6_LOCAL_MAX, nla, seg6_local_policy,
799d1df6fd8SDavid Lebrun 			       extack);
800d1df6fd8SDavid Lebrun 
801d1df6fd8SDavid Lebrun 	if (err < 0)
802d1df6fd8SDavid Lebrun 		return err;
803d1df6fd8SDavid Lebrun 
804d1df6fd8SDavid Lebrun 	if (!tb[SEG6_LOCAL_ACTION])
805d1df6fd8SDavid Lebrun 		return -EINVAL;
806d1df6fd8SDavid Lebrun 
807d1df6fd8SDavid Lebrun 	newts = lwtunnel_state_alloc(sizeof(*slwt));
808d1df6fd8SDavid Lebrun 	if (!newts)
809d1df6fd8SDavid Lebrun 		return -ENOMEM;
810d1df6fd8SDavid Lebrun 
811d1df6fd8SDavid Lebrun 	slwt = seg6_local_lwtunnel(newts);
812d1df6fd8SDavid Lebrun 	slwt->action = nla_get_u32(tb[SEG6_LOCAL_ACTION]);
813d1df6fd8SDavid Lebrun 
814d1df6fd8SDavid Lebrun 	err = parse_nla_action(tb, slwt);
815d1df6fd8SDavid Lebrun 	if (err < 0)
816d1df6fd8SDavid Lebrun 		goto out_free;
817d1df6fd8SDavid Lebrun 
818d1df6fd8SDavid Lebrun 	newts->type = LWTUNNEL_ENCAP_SEG6_LOCAL;
819d1df6fd8SDavid Lebrun 	newts->flags = LWTUNNEL_STATE_INPUT_REDIRECT;
820d1df6fd8SDavid Lebrun 	newts->headroom = slwt->headroom;
821d1df6fd8SDavid Lebrun 
822d1df6fd8SDavid Lebrun 	*ts = newts;
823d1df6fd8SDavid Lebrun 
824d1df6fd8SDavid Lebrun 	return 0;
825d1df6fd8SDavid Lebrun 
826d1df6fd8SDavid Lebrun out_free:
827d1df6fd8SDavid Lebrun 	kfree(slwt->srh);
828d1df6fd8SDavid Lebrun 	kfree(newts);
829d1df6fd8SDavid Lebrun 	return err;
830d1df6fd8SDavid Lebrun }
831d1df6fd8SDavid Lebrun 
832d1df6fd8SDavid Lebrun static void seg6_local_destroy_state(struct lwtunnel_state *lwt)
833d1df6fd8SDavid Lebrun {
834d1df6fd8SDavid Lebrun 	struct seg6_local_lwt *slwt = seg6_local_lwtunnel(lwt);
835d1df6fd8SDavid Lebrun 
836d1df6fd8SDavid Lebrun 	kfree(slwt->srh);
837d1df6fd8SDavid Lebrun }
838d1df6fd8SDavid Lebrun 
839d1df6fd8SDavid Lebrun static int seg6_local_fill_encap(struct sk_buff *skb,
840d1df6fd8SDavid Lebrun 				 struct lwtunnel_state *lwt)
841d1df6fd8SDavid Lebrun {
842d1df6fd8SDavid Lebrun 	struct seg6_local_lwt *slwt = seg6_local_lwtunnel(lwt);
843d1df6fd8SDavid Lebrun 	struct seg6_action_param *param;
844d1df6fd8SDavid Lebrun 	int i, err;
845d1df6fd8SDavid Lebrun 
846d1df6fd8SDavid Lebrun 	if (nla_put_u32(skb, SEG6_LOCAL_ACTION, slwt->action))
847d1df6fd8SDavid Lebrun 		return -EMSGSIZE;
848d1df6fd8SDavid Lebrun 
849d1df6fd8SDavid Lebrun 	for (i = 0; i < SEG6_LOCAL_MAX + 1; i++) {
850d1df6fd8SDavid Lebrun 		if (slwt->desc->attrs & (1 << i)) {
851d1df6fd8SDavid Lebrun 			param = &seg6_action_params[i];
852d1df6fd8SDavid Lebrun 			err = param->put(skb, slwt);
853d1df6fd8SDavid Lebrun 			if (err < 0)
854d1df6fd8SDavid Lebrun 				return err;
855d1df6fd8SDavid Lebrun 		}
856d1df6fd8SDavid Lebrun 	}
857d1df6fd8SDavid Lebrun 
858d1df6fd8SDavid Lebrun 	return 0;
859d1df6fd8SDavid Lebrun }
860d1df6fd8SDavid Lebrun 
861d1df6fd8SDavid Lebrun static int seg6_local_get_encap_size(struct lwtunnel_state *lwt)
862d1df6fd8SDavid Lebrun {
863d1df6fd8SDavid Lebrun 	struct seg6_local_lwt *slwt = seg6_local_lwtunnel(lwt);
864d1df6fd8SDavid Lebrun 	unsigned long attrs;
865d1df6fd8SDavid Lebrun 	int nlsize;
866d1df6fd8SDavid Lebrun 
867d1df6fd8SDavid Lebrun 	nlsize = nla_total_size(4); /* action */
868d1df6fd8SDavid Lebrun 
869d1df6fd8SDavid Lebrun 	attrs = slwt->desc->attrs;
870d1df6fd8SDavid Lebrun 
871d1df6fd8SDavid Lebrun 	if (attrs & (1 << SEG6_LOCAL_SRH))
872d1df6fd8SDavid Lebrun 		nlsize += nla_total_size((slwt->srh->hdrlen + 1) << 3);
873d1df6fd8SDavid Lebrun 
874d1df6fd8SDavid Lebrun 	if (attrs & (1 << SEG6_LOCAL_TABLE))
875d1df6fd8SDavid Lebrun 		nlsize += nla_total_size(4);
876d1df6fd8SDavid Lebrun 
877d1df6fd8SDavid Lebrun 	if (attrs & (1 << SEG6_LOCAL_NH4))
878d1df6fd8SDavid Lebrun 		nlsize += nla_total_size(4);
879d1df6fd8SDavid Lebrun 
880d1df6fd8SDavid Lebrun 	if (attrs & (1 << SEG6_LOCAL_NH6))
881d1df6fd8SDavid Lebrun 		nlsize += nla_total_size(16);
882d1df6fd8SDavid Lebrun 
883d1df6fd8SDavid Lebrun 	if (attrs & (1 << SEG6_LOCAL_IIF))
884d1df6fd8SDavid Lebrun 		nlsize += nla_total_size(4);
885d1df6fd8SDavid Lebrun 
886d1df6fd8SDavid Lebrun 	if (attrs & (1 << SEG6_LOCAL_OIF))
887d1df6fd8SDavid Lebrun 		nlsize += nla_total_size(4);
888d1df6fd8SDavid Lebrun 
889d1df6fd8SDavid Lebrun 	return nlsize;
890d1df6fd8SDavid Lebrun }
891d1df6fd8SDavid Lebrun 
892d1df6fd8SDavid Lebrun static int seg6_local_cmp_encap(struct lwtunnel_state *a,
893d1df6fd8SDavid Lebrun 				struct lwtunnel_state *b)
894d1df6fd8SDavid Lebrun {
895d1df6fd8SDavid Lebrun 	struct seg6_local_lwt *slwt_a, *slwt_b;
896d1df6fd8SDavid Lebrun 	struct seg6_action_param *param;
897d1df6fd8SDavid Lebrun 	int i;
898d1df6fd8SDavid Lebrun 
899d1df6fd8SDavid Lebrun 	slwt_a = seg6_local_lwtunnel(a);
900d1df6fd8SDavid Lebrun 	slwt_b = seg6_local_lwtunnel(b);
901d1df6fd8SDavid Lebrun 
902d1df6fd8SDavid Lebrun 	if (slwt_a->action != slwt_b->action)
903d1df6fd8SDavid Lebrun 		return 1;
904d1df6fd8SDavid Lebrun 
905d1df6fd8SDavid Lebrun 	if (slwt_a->desc->attrs != slwt_b->desc->attrs)
906d1df6fd8SDavid Lebrun 		return 1;
907d1df6fd8SDavid Lebrun 
908d1df6fd8SDavid Lebrun 	for (i = 0; i < SEG6_LOCAL_MAX + 1; i++) {
909d1df6fd8SDavid Lebrun 		if (slwt_a->desc->attrs & (1 << i)) {
910d1df6fd8SDavid Lebrun 			param = &seg6_action_params[i];
911d1df6fd8SDavid Lebrun 			if (param->cmp(slwt_a, slwt_b))
912d1df6fd8SDavid Lebrun 				return 1;
913d1df6fd8SDavid Lebrun 		}
914d1df6fd8SDavid Lebrun 	}
915d1df6fd8SDavid Lebrun 
916d1df6fd8SDavid Lebrun 	return 0;
917d1df6fd8SDavid Lebrun }
918d1df6fd8SDavid Lebrun 
919d1df6fd8SDavid Lebrun static const struct lwtunnel_encap_ops seg6_local_ops = {
920d1df6fd8SDavid Lebrun 	.build_state	= seg6_local_build_state,
921d1df6fd8SDavid Lebrun 	.destroy_state	= seg6_local_destroy_state,
922d1df6fd8SDavid Lebrun 	.input		= seg6_local_input,
923d1df6fd8SDavid Lebrun 	.fill_encap	= seg6_local_fill_encap,
924d1df6fd8SDavid Lebrun 	.get_encap_size	= seg6_local_get_encap_size,
925d1df6fd8SDavid Lebrun 	.cmp_encap	= seg6_local_cmp_encap,
926d1df6fd8SDavid Lebrun 	.owner		= THIS_MODULE,
927d1df6fd8SDavid Lebrun };
928d1df6fd8SDavid Lebrun 
929d1df6fd8SDavid Lebrun int __init seg6_local_init(void)
930d1df6fd8SDavid Lebrun {
931d1df6fd8SDavid Lebrun 	return lwtunnel_encap_add_ops(&seg6_local_ops,
932d1df6fd8SDavid Lebrun 				      LWTUNNEL_ENCAP_SEG6_LOCAL);
933d1df6fd8SDavid Lebrun }
934d1df6fd8SDavid Lebrun 
935d1df6fd8SDavid Lebrun void seg6_local_exit(void)
936d1df6fd8SDavid Lebrun {
937d1df6fd8SDavid Lebrun 	lwtunnel_encap_del_ops(&seg6_local_ops, LWTUNNEL_ENCAP_SEG6_LOCAL);
938d1df6fd8SDavid Lebrun }
939