xref: /openbmc/linux/net/ipv6/seg6_local.c (revision 5829d70b0b6cd055e7a9af3abe573927e919e30b)
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
33891ef8ddSDavid Lebrun #include <linux/etherdevice.h>
34d1df6fd8SDavid Lebrun 
35d1df6fd8SDavid Lebrun struct seg6_local_lwt;
36d1df6fd8SDavid Lebrun 
37d1df6fd8SDavid Lebrun struct seg6_action_desc {
38d1df6fd8SDavid Lebrun 	int action;
39d1df6fd8SDavid Lebrun 	unsigned long attrs;
40d1df6fd8SDavid Lebrun 	int (*input)(struct sk_buff *skb, struct seg6_local_lwt *slwt);
41d1df6fd8SDavid Lebrun 	int static_headroom;
42d1df6fd8SDavid Lebrun };
43d1df6fd8SDavid Lebrun 
44d1df6fd8SDavid Lebrun struct seg6_local_lwt {
45d1df6fd8SDavid Lebrun 	int action;
46d1df6fd8SDavid Lebrun 	struct ipv6_sr_hdr *srh;
47d1df6fd8SDavid Lebrun 	int table;
48d1df6fd8SDavid Lebrun 	struct in_addr nh4;
49d1df6fd8SDavid Lebrun 	struct in6_addr nh6;
50d1df6fd8SDavid Lebrun 	int iif;
51d1df6fd8SDavid Lebrun 	int oif;
52d1df6fd8SDavid Lebrun 
53d1df6fd8SDavid Lebrun 	int headroom;
54d1df6fd8SDavid Lebrun 	struct seg6_action_desc *desc;
55d1df6fd8SDavid Lebrun };
56d1df6fd8SDavid Lebrun 
57d1df6fd8SDavid Lebrun static struct seg6_local_lwt *seg6_local_lwtunnel(struct lwtunnel_state *lwt)
58d1df6fd8SDavid Lebrun {
59d1df6fd8SDavid Lebrun 	return (struct seg6_local_lwt *)lwt->data;
60d1df6fd8SDavid Lebrun }
61d1df6fd8SDavid Lebrun 
62140f04c3SDavid Lebrun static struct ipv6_sr_hdr *get_srh(struct sk_buff *skb)
63140f04c3SDavid Lebrun {
64140f04c3SDavid Lebrun 	struct ipv6_sr_hdr *srh;
65*5829d70bSAhmed Abdelsalam 	int len, srhoff = 0;
66140f04c3SDavid Lebrun 
67*5829d70bSAhmed Abdelsalam 	if (ipv6_find_hdr(skb, &srhoff, IPPROTO_ROUTING, NULL, NULL) < 0)
68140f04c3SDavid Lebrun 		return NULL;
69140f04c3SDavid Lebrun 
70*5829d70bSAhmed Abdelsalam 	if (!pskb_may_pull(skb, srhoff + sizeof(*srh)))
71*5829d70bSAhmed Abdelsalam 		return NULL;
72*5829d70bSAhmed Abdelsalam 
73*5829d70bSAhmed Abdelsalam 	srh = (struct ipv6_sr_hdr *)(skb->data + srhoff);
74*5829d70bSAhmed Abdelsalam 
75*5829d70bSAhmed Abdelsalam 	/* make sure it's a Segment Routing header (Routing Type 4) */
76*5829d70bSAhmed Abdelsalam 	if (srh->type != IPV6_SRCRT_TYPE_4)
77*5829d70bSAhmed Abdelsalam 		return NULL;
78*5829d70bSAhmed Abdelsalam 
79140f04c3SDavid Lebrun 	len = (srh->hdrlen + 1) << 3;
80140f04c3SDavid Lebrun 
81*5829d70bSAhmed Abdelsalam 	if (!pskb_may_pull(skb, srhoff + len))
82140f04c3SDavid Lebrun 		return NULL;
83140f04c3SDavid Lebrun 
84140f04c3SDavid Lebrun 	if (!seg6_validate_srh(srh, len))
85140f04c3SDavid Lebrun 		return NULL;
86140f04c3SDavid Lebrun 
87140f04c3SDavid Lebrun 	return srh;
88140f04c3SDavid Lebrun }
89140f04c3SDavid Lebrun 
90140f04c3SDavid Lebrun static struct ipv6_sr_hdr *get_and_validate_srh(struct sk_buff *skb)
91140f04c3SDavid Lebrun {
92140f04c3SDavid Lebrun 	struct ipv6_sr_hdr *srh;
93140f04c3SDavid Lebrun 
94140f04c3SDavid Lebrun 	srh = get_srh(skb);
95140f04c3SDavid Lebrun 	if (!srh)
96140f04c3SDavid Lebrun 		return NULL;
97140f04c3SDavid Lebrun 
98140f04c3SDavid Lebrun 	if (srh->segments_left == 0)
99140f04c3SDavid Lebrun 		return NULL;
100140f04c3SDavid Lebrun 
101140f04c3SDavid Lebrun #ifdef CONFIG_IPV6_SEG6_HMAC
102140f04c3SDavid Lebrun 	if (!seg6_hmac_validate_skb(skb))
103140f04c3SDavid Lebrun 		return NULL;
104140f04c3SDavid Lebrun #endif
105140f04c3SDavid Lebrun 
106140f04c3SDavid Lebrun 	return srh;
107140f04c3SDavid Lebrun }
108140f04c3SDavid Lebrun 
109d7a669ddSDavid Lebrun static bool decap_and_validate(struct sk_buff *skb, int proto)
110d7a669ddSDavid Lebrun {
111d7a669ddSDavid Lebrun 	struct ipv6_sr_hdr *srh;
112d7a669ddSDavid Lebrun 	unsigned int off = 0;
113d7a669ddSDavid Lebrun 
114d7a669ddSDavid Lebrun 	srh = get_srh(skb);
115d7a669ddSDavid Lebrun 	if (srh && srh->segments_left > 0)
116d7a669ddSDavid Lebrun 		return false;
117d7a669ddSDavid Lebrun 
118d7a669ddSDavid Lebrun #ifdef CONFIG_IPV6_SEG6_HMAC
119d7a669ddSDavid Lebrun 	if (srh && !seg6_hmac_validate_skb(skb))
120d7a669ddSDavid Lebrun 		return false;
121d7a669ddSDavid Lebrun #endif
122d7a669ddSDavid Lebrun 
123d7a669ddSDavid Lebrun 	if (ipv6_find_hdr(skb, &off, proto, NULL, NULL) < 0)
124d7a669ddSDavid Lebrun 		return false;
125d7a669ddSDavid Lebrun 
126d7a669ddSDavid Lebrun 	if (!pskb_pull(skb, off))
127d7a669ddSDavid Lebrun 		return false;
128d7a669ddSDavid Lebrun 
129d7a669ddSDavid Lebrun 	skb_postpull_rcsum(skb, skb_network_header(skb), off);
130d7a669ddSDavid Lebrun 
131d7a669ddSDavid Lebrun 	skb_reset_network_header(skb);
132d7a669ddSDavid Lebrun 	skb_reset_transport_header(skb);
133d7a669ddSDavid Lebrun 	skb->encapsulation = 0;
134d7a669ddSDavid Lebrun 
135d7a669ddSDavid Lebrun 	return true;
136d7a669ddSDavid Lebrun }
137d7a669ddSDavid Lebrun 
138d7a669ddSDavid Lebrun static void advance_nextseg(struct ipv6_sr_hdr *srh, struct in6_addr *daddr)
139d7a669ddSDavid Lebrun {
140d7a669ddSDavid Lebrun 	struct in6_addr *addr;
141d7a669ddSDavid Lebrun 
142d7a669ddSDavid Lebrun 	srh->segments_left--;
143d7a669ddSDavid Lebrun 	addr = srh->segments + srh->segments_left;
144d7a669ddSDavid Lebrun 	*daddr = *addr;
145d7a669ddSDavid Lebrun }
146d7a669ddSDavid Lebrun 
147d7a669ddSDavid Lebrun static void lookup_nexthop(struct sk_buff *skb, struct in6_addr *nhaddr,
148d7a669ddSDavid Lebrun 			   u32 tbl_id)
149d7a669ddSDavid Lebrun {
150d7a669ddSDavid Lebrun 	struct net *net = dev_net(skb->dev);
151d7a669ddSDavid Lebrun 	struct ipv6hdr *hdr = ipv6_hdr(skb);
152d7a669ddSDavid Lebrun 	int flags = RT6_LOOKUP_F_HAS_SADDR;
153d7a669ddSDavid Lebrun 	struct dst_entry *dst = NULL;
154d7a669ddSDavid Lebrun 	struct rt6_info *rt;
155d7a669ddSDavid Lebrun 	struct flowi6 fl6;
156d7a669ddSDavid Lebrun 
157d7a669ddSDavid Lebrun 	fl6.flowi6_iif = skb->dev->ifindex;
158d7a669ddSDavid Lebrun 	fl6.daddr = nhaddr ? *nhaddr : hdr->daddr;
159d7a669ddSDavid Lebrun 	fl6.saddr = hdr->saddr;
160d7a669ddSDavid Lebrun 	fl6.flowlabel = ip6_flowinfo(hdr);
161d7a669ddSDavid Lebrun 	fl6.flowi6_mark = skb->mark;
162d7a669ddSDavid Lebrun 	fl6.flowi6_proto = hdr->nexthdr;
163d7a669ddSDavid Lebrun 
164d7a669ddSDavid Lebrun 	if (nhaddr)
165d7a669ddSDavid Lebrun 		fl6.flowi6_flags = FLOWI_FLAG_KNOWN_NH;
166d7a669ddSDavid Lebrun 
167d7a669ddSDavid Lebrun 	if (!tbl_id) {
168d7a669ddSDavid Lebrun 		dst = ip6_route_input_lookup(net, skb->dev, &fl6, flags);
169d7a669ddSDavid Lebrun 	} else {
170d7a669ddSDavid Lebrun 		struct fib6_table *table;
171d7a669ddSDavid Lebrun 
172d7a669ddSDavid Lebrun 		table = fib6_get_table(net, tbl_id);
173d7a669ddSDavid Lebrun 		if (!table)
174d7a669ddSDavid Lebrun 			goto out;
175d7a669ddSDavid Lebrun 
176d7a669ddSDavid Lebrun 		rt = ip6_pol_route(net, table, 0, &fl6, flags);
177d7a669ddSDavid Lebrun 		dst = &rt->dst;
178d7a669ddSDavid Lebrun 	}
179d7a669ddSDavid Lebrun 
180d7a669ddSDavid Lebrun 	if (dst && dst->dev->flags & IFF_LOOPBACK && !dst->error) {
181d7a669ddSDavid Lebrun 		dst_release(dst);
182d7a669ddSDavid Lebrun 		dst = NULL;
183d7a669ddSDavid Lebrun 	}
184d7a669ddSDavid Lebrun 
185d7a669ddSDavid Lebrun out:
186d7a669ddSDavid Lebrun 	if (!dst) {
187d7a669ddSDavid Lebrun 		rt = net->ipv6.ip6_blk_hole_entry;
188d7a669ddSDavid Lebrun 		dst = &rt->dst;
189d7a669ddSDavid Lebrun 		dst_hold(dst);
190d7a669ddSDavid Lebrun 	}
191d7a669ddSDavid Lebrun 
192d7a669ddSDavid Lebrun 	skb_dst_drop(skb);
193d7a669ddSDavid Lebrun 	skb_dst_set(skb, dst);
194d7a669ddSDavid Lebrun }
195d7a669ddSDavid Lebrun 
196140f04c3SDavid Lebrun /* regular endpoint function */
197140f04c3SDavid Lebrun static int input_action_end(struct sk_buff *skb, struct seg6_local_lwt *slwt)
198140f04c3SDavid Lebrun {
199140f04c3SDavid Lebrun 	struct ipv6_sr_hdr *srh;
200140f04c3SDavid Lebrun 
201140f04c3SDavid Lebrun 	srh = get_and_validate_srh(skb);
202140f04c3SDavid Lebrun 	if (!srh)
203140f04c3SDavid Lebrun 		goto drop;
204140f04c3SDavid Lebrun 
205d7a669ddSDavid Lebrun 	advance_nextseg(srh, &ipv6_hdr(skb)->daddr);
206140f04c3SDavid Lebrun 
207d7a669ddSDavid Lebrun 	lookup_nexthop(skb, NULL, 0);
208140f04c3SDavid Lebrun 
209140f04c3SDavid Lebrun 	return dst_input(skb);
210140f04c3SDavid Lebrun 
211140f04c3SDavid Lebrun drop:
212140f04c3SDavid Lebrun 	kfree_skb(skb);
213140f04c3SDavid Lebrun 	return -EINVAL;
214140f04c3SDavid Lebrun }
215140f04c3SDavid Lebrun 
216140f04c3SDavid Lebrun /* regular endpoint, and forward to specified nexthop */
217140f04c3SDavid Lebrun static int input_action_end_x(struct sk_buff *skb, struct seg6_local_lwt *slwt)
218140f04c3SDavid Lebrun {
219140f04c3SDavid Lebrun 	struct ipv6_sr_hdr *srh;
220140f04c3SDavid Lebrun 
221140f04c3SDavid Lebrun 	srh = get_and_validate_srh(skb);
222140f04c3SDavid Lebrun 	if (!srh)
223140f04c3SDavid Lebrun 		goto drop;
224140f04c3SDavid Lebrun 
225d7a669ddSDavid Lebrun 	advance_nextseg(srh, &ipv6_hdr(skb)->daddr);
226140f04c3SDavid Lebrun 
227d7a669ddSDavid Lebrun 	lookup_nexthop(skb, &slwt->nh6, 0);
228140f04c3SDavid Lebrun 
229140f04c3SDavid Lebrun 	return dst_input(skb);
230140f04c3SDavid Lebrun 
231140f04c3SDavid Lebrun drop:
232140f04c3SDavid Lebrun 	kfree_skb(skb);
233140f04c3SDavid Lebrun 	return -EINVAL;
234140f04c3SDavid Lebrun }
235140f04c3SDavid Lebrun 
236891ef8ddSDavid Lebrun static int input_action_end_t(struct sk_buff *skb, struct seg6_local_lwt *slwt)
237891ef8ddSDavid Lebrun {
238891ef8ddSDavid Lebrun 	struct ipv6_sr_hdr *srh;
239891ef8ddSDavid Lebrun 
240891ef8ddSDavid Lebrun 	srh = get_and_validate_srh(skb);
241891ef8ddSDavid Lebrun 	if (!srh)
242891ef8ddSDavid Lebrun 		goto drop;
243891ef8ddSDavid Lebrun 
244891ef8ddSDavid Lebrun 	advance_nextseg(srh, &ipv6_hdr(skb)->daddr);
245891ef8ddSDavid Lebrun 
246891ef8ddSDavid Lebrun 	lookup_nexthop(skb, NULL, slwt->table);
247891ef8ddSDavid Lebrun 
248891ef8ddSDavid Lebrun 	return dst_input(skb);
249891ef8ddSDavid Lebrun 
250891ef8ddSDavid Lebrun drop:
251891ef8ddSDavid Lebrun 	kfree_skb(skb);
252891ef8ddSDavid Lebrun 	return -EINVAL;
253891ef8ddSDavid Lebrun }
254891ef8ddSDavid Lebrun 
255891ef8ddSDavid Lebrun /* decapsulate and forward inner L2 frame on specified interface */
256891ef8ddSDavid Lebrun static int input_action_end_dx2(struct sk_buff *skb,
257891ef8ddSDavid Lebrun 				struct seg6_local_lwt *slwt)
258891ef8ddSDavid Lebrun {
259891ef8ddSDavid Lebrun 	struct net *net = dev_net(skb->dev);
260891ef8ddSDavid Lebrun 	struct net_device *odev;
261891ef8ddSDavid Lebrun 	struct ethhdr *eth;
262891ef8ddSDavid Lebrun 
263891ef8ddSDavid Lebrun 	if (!decap_and_validate(skb, NEXTHDR_NONE))
264891ef8ddSDavid Lebrun 		goto drop;
265891ef8ddSDavid Lebrun 
266891ef8ddSDavid Lebrun 	if (!pskb_may_pull(skb, ETH_HLEN))
267891ef8ddSDavid Lebrun 		goto drop;
268891ef8ddSDavid Lebrun 
269891ef8ddSDavid Lebrun 	skb_reset_mac_header(skb);
270891ef8ddSDavid Lebrun 	eth = (struct ethhdr *)skb->data;
271891ef8ddSDavid Lebrun 
272891ef8ddSDavid Lebrun 	/* To determine the frame's protocol, we assume it is 802.3. This avoids
273891ef8ddSDavid Lebrun 	 * a call to eth_type_trans(), which is not really relevant for our
274891ef8ddSDavid Lebrun 	 * use case.
275891ef8ddSDavid Lebrun 	 */
276891ef8ddSDavid Lebrun 	if (!eth_proto_is_802_3(eth->h_proto))
277891ef8ddSDavid Lebrun 		goto drop;
278891ef8ddSDavid Lebrun 
279891ef8ddSDavid Lebrun 	odev = dev_get_by_index_rcu(net, slwt->oif);
280891ef8ddSDavid Lebrun 	if (!odev)
281891ef8ddSDavid Lebrun 		goto drop;
282891ef8ddSDavid Lebrun 
283891ef8ddSDavid Lebrun 	/* As we accept Ethernet frames, make sure the egress device is of
284891ef8ddSDavid Lebrun 	 * the correct type.
285891ef8ddSDavid Lebrun 	 */
286891ef8ddSDavid Lebrun 	if (odev->type != ARPHRD_ETHER)
287891ef8ddSDavid Lebrun 		goto drop;
288891ef8ddSDavid Lebrun 
289891ef8ddSDavid Lebrun 	if (!(odev->flags & IFF_UP) || !netif_carrier_ok(odev))
290891ef8ddSDavid Lebrun 		goto drop;
291891ef8ddSDavid Lebrun 
292891ef8ddSDavid Lebrun 	skb_orphan(skb);
293891ef8ddSDavid Lebrun 
294891ef8ddSDavid Lebrun 	if (skb_warn_if_lro(skb))
295891ef8ddSDavid Lebrun 		goto drop;
296891ef8ddSDavid Lebrun 
297891ef8ddSDavid Lebrun 	skb_forward_csum(skb);
298891ef8ddSDavid Lebrun 
299891ef8ddSDavid Lebrun 	if (skb->len - ETH_HLEN > odev->mtu)
300891ef8ddSDavid Lebrun 		goto drop;
301891ef8ddSDavid Lebrun 
302891ef8ddSDavid Lebrun 	skb->dev = odev;
303891ef8ddSDavid Lebrun 	skb->protocol = eth->h_proto;
304891ef8ddSDavid Lebrun 
305891ef8ddSDavid Lebrun 	return dev_queue_xmit(skb);
306891ef8ddSDavid Lebrun 
307891ef8ddSDavid Lebrun drop:
308891ef8ddSDavid Lebrun 	kfree_skb(skb);
309891ef8ddSDavid Lebrun 	return -EINVAL;
310891ef8ddSDavid Lebrun }
311891ef8ddSDavid Lebrun 
312140f04c3SDavid Lebrun /* decapsulate and forward to specified nexthop */
313140f04c3SDavid Lebrun static int input_action_end_dx6(struct sk_buff *skb,
314140f04c3SDavid Lebrun 				struct seg6_local_lwt *slwt)
315140f04c3SDavid Lebrun {
316d7a669ddSDavid Lebrun 	struct in6_addr *nhaddr = NULL;
317140f04c3SDavid Lebrun 
318140f04c3SDavid Lebrun 	/* this function accepts IPv6 encapsulated packets, with either
319140f04c3SDavid Lebrun 	 * an SRH with SL=0, or no SRH.
320140f04c3SDavid Lebrun 	 */
321140f04c3SDavid Lebrun 
322d7a669ddSDavid Lebrun 	if (!decap_and_validate(skb, IPPROTO_IPV6))
323140f04c3SDavid Lebrun 		goto drop;
324140f04c3SDavid Lebrun 
325d7a669ddSDavid Lebrun 	if (!pskb_may_pull(skb, sizeof(struct ipv6hdr)))
326140f04c3SDavid Lebrun 		goto drop;
327140f04c3SDavid Lebrun 
328140f04c3SDavid Lebrun 	/* The inner packet is not associated to any local interface,
329140f04c3SDavid Lebrun 	 * so we do not call netif_rx().
330140f04c3SDavid Lebrun 	 *
331140f04c3SDavid Lebrun 	 * If slwt->nh6 is set to ::, then lookup the nexthop for the
332140f04c3SDavid Lebrun 	 * inner packet's DA. Otherwise, use the specified nexthop.
333140f04c3SDavid Lebrun 	 */
334140f04c3SDavid Lebrun 
335d7a669ddSDavid Lebrun 	if (!ipv6_addr_any(&slwt->nh6))
336d7a669ddSDavid Lebrun 		nhaddr = &slwt->nh6;
337140f04c3SDavid Lebrun 
338d7a669ddSDavid Lebrun 	lookup_nexthop(skb, nhaddr, 0);
339140f04c3SDavid Lebrun 
340140f04c3SDavid Lebrun 	return dst_input(skb);
341140f04c3SDavid Lebrun drop:
342140f04c3SDavid Lebrun 	kfree_skb(skb);
343140f04c3SDavid Lebrun 	return -EINVAL;
344140f04c3SDavid Lebrun }
345140f04c3SDavid Lebrun 
346891ef8ddSDavid Lebrun static int input_action_end_dx4(struct sk_buff *skb,
347891ef8ddSDavid Lebrun 				struct seg6_local_lwt *slwt)
348891ef8ddSDavid Lebrun {
349891ef8ddSDavid Lebrun 	struct iphdr *iph;
350891ef8ddSDavid Lebrun 	__be32 nhaddr;
351891ef8ddSDavid Lebrun 	int err;
352891ef8ddSDavid Lebrun 
353891ef8ddSDavid Lebrun 	if (!decap_and_validate(skb, IPPROTO_IPIP))
354891ef8ddSDavid Lebrun 		goto drop;
355891ef8ddSDavid Lebrun 
356891ef8ddSDavid Lebrun 	if (!pskb_may_pull(skb, sizeof(struct iphdr)))
357891ef8ddSDavid Lebrun 		goto drop;
358891ef8ddSDavid Lebrun 
359891ef8ddSDavid Lebrun 	skb->protocol = htons(ETH_P_IP);
360891ef8ddSDavid Lebrun 
361891ef8ddSDavid Lebrun 	iph = ip_hdr(skb);
362891ef8ddSDavid Lebrun 
363891ef8ddSDavid Lebrun 	nhaddr = slwt->nh4.s_addr ?: iph->daddr;
364891ef8ddSDavid Lebrun 
365891ef8ddSDavid Lebrun 	skb_dst_drop(skb);
366891ef8ddSDavid Lebrun 
367891ef8ddSDavid Lebrun 	err = ip_route_input(skb, nhaddr, iph->saddr, 0, skb->dev);
368891ef8ddSDavid Lebrun 	if (err)
369891ef8ddSDavid Lebrun 		goto drop;
370891ef8ddSDavid Lebrun 
371891ef8ddSDavid Lebrun 	return dst_input(skb);
372891ef8ddSDavid Lebrun 
373891ef8ddSDavid Lebrun drop:
374891ef8ddSDavid Lebrun 	kfree_skb(skb);
375891ef8ddSDavid Lebrun 	return -EINVAL;
376891ef8ddSDavid Lebrun }
377891ef8ddSDavid Lebrun 
378891ef8ddSDavid Lebrun static int input_action_end_dt6(struct sk_buff *skb,
379891ef8ddSDavid Lebrun 				struct seg6_local_lwt *slwt)
380891ef8ddSDavid Lebrun {
381891ef8ddSDavid Lebrun 	if (!decap_and_validate(skb, IPPROTO_IPV6))
382891ef8ddSDavid Lebrun 		goto drop;
383891ef8ddSDavid Lebrun 
384891ef8ddSDavid Lebrun 	if (!pskb_may_pull(skb, sizeof(struct ipv6hdr)))
385891ef8ddSDavid Lebrun 		goto drop;
386891ef8ddSDavid Lebrun 
387891ef8ddSDavid Lebrun 	lookup_nexthop(skb, NULL, slwt->table);
388891ef8ddSDavid Lebrun 
389891ef8ddSDavid Lebrun 	return dst_input(skb);
390891ef8ddSDavid Lebrun 
391891ef8ddSDavid Lebrun drop:
392891ef8ddSDavid Lebrun 	kfree_skb(skb);
393891ef8ddSDavid Lebrun 	return -EINVAL;
394891ef8ddSDavid Lebrun }
395891ef8ddSDavid Lebrun 
396140f04c3SDavid Lebrun /* push an SRH on top of the current one */
397140f04c3SDavid Lebrun static int input_action_end_b6(struct sk_buff *skb, struct seg6_local_lwt *slwt)
398140f04c3SDavid Lebrun {
399140f04c3SDavid Lebrun 	struct ipv6_sr_hdr *srh;
400140f04c3SDavid Lebrun 	int err = -EINVAL;
401140f04c3SDavid Lebrun 
402140f04c3SDavid Lebrun 	srh = get_and_validate_srh(skb);
403140f04c3SDavid Lebrun 	if (!srh)
404140f04c3SDavid Lebrun 		goto drop;
405140f04c3SDavid Lebrun 
406140f04c3SDavid Lebrun 	err = seg6_do_srh_inline(skb, slwt->srh);
407140f04c3SDavid Lebrun 	if (err)
408140f04c3SDavid Lebrun 		goto drop;
409140f04c3SDavid Lebrun 
410140f04c3SDavid Lebrun 	ipv6_hdr(skb)->payload_len = htons(skb->len - sizeof(struct ipv6hdr));
411140f04c3SDavid Lebrun 	skb_set_transport_header(skb, sizeof(struct ipv6hdr));
412140f04c3SDavid Lebrun 
413d7a669ddSDavid Lebrun 	lookup_nexthop(skb, NULL, 0);
414140f04c3SDavid Lebrun 
415140f04c3SDavid Lebrun 	return dst_input(skb);
416140f04c3SDavid Lebrun 
417140f04c3SDavid Lebrun drop:
418140f04c3SDavid Lebrun 	kfree_skb(skb);
419140f04c3SDavid Lebrun 	return err;
420140f04c3SDavid Lebrun }
421140f04c3SDavid Lebrun 
422140f04c3SDavid Lebrun /* encapsulate within an outer IPv6 header and a specified SRH */
423140f04c3SDavid Lebrun static int input_action_end_b6_encap(struct sk_buff *skb,
424140f04c3SDavid Lebrun 				     struct seg6_local_lwt *slwt)
425140f04c3SDavid Lebrun {
426140f04c3SDavid Lebrun 	struct ipv6_sr_hdr *srh;
427140f04c3SDavid Lebrun 	int err = -EINVAL;
428140f04c3SDavid Lebrun 
429140f04c3SDavid Lebrun 	srh = get_and_validate_srh(skb);
430140f04c3SDavid Lebrun 	if (!srh)
431140f04c3SDavid Lebrun 		goto drop;
432140f04c3SDavid Lebrun 
433d7a669ddSDavid Lebrun 	advance_nextseg(srh, &ipv6_hdr(skb)->daddr);
434140f04c3SDavid Lebrun 
435140f04c3SDavid Lebrun 	skb_reset_inner_headers(skb);
436140f04c3SDavid Lebrun 	skb->encapsulation = 1;
437140f04c3SDavid Lebrun 
43832d99d0bSDavid Lebrun 	err = seg6_do_srh_encap(skb, slwt->srh, IPPROTO_IPV6);
439140f04c3SDavid Lebrun 	if (err)
440140f04c3SDavid Lebrun 		goto drop;
441140f04c3SDavid Lebrun 
442140f04c3SDavid Lebrun 	ipv6_hdr(skb)->payload_len = htons(skb->len - sizeof(struct ipv6hdr));
443140f04c3SDavid Lebrun 	skb_set_transport_header(skb, sizeof(struct ipv6hdr));
444140f04c3SDavid Lebrun 
445d7a669ddSDavid Lebrun 	lookup_nexthop(skb, NULL, 0);
446140f04c3SDavid Lebrun 
447140f04c3SDavid Lebrun 	return dst_input(skb);
448140f04c3SDavid Lebrun 
449140f04c3SDavid Lebrun drop:
450140f04c3SDavid Lebrun 	kfree_skb(skb);
451140f04c3SDavid Lebrun 	return err;
452140f04c3SDavid Lebrun }
453140f04c3SDavid Lebrun 
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 
508d1df6fd8SDavid Lebrun 	count = sizeof(seg6_action_table) / sizeof(struct seg6_action_desc);
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