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 33d1df6fd8SDavid Lebrun 34d1df6fd8SDavid Lebrun struct seg6_local_lwt; 35d1df6fd8SDavid Lebrun 36d1df6fd8SDavid Lebrun struct seg6_action_desc { 37d1df6fd8SDavid Lebrun int action; 38d1df6fd8SDavid Lebrun unsigned long attrs; 39d1df6fd8SDavid Lebrun int (*input)(struct sk_buff *skb, struct seg6_local_lwt *slwt); 40d1df6fd8SDavid Lebrun int static_headroom; 41d1df6fd8SDavid Lebrun }; 42d1df6fd8SDavid Lebrun 43d1df6fd8SDavid Lebrun struct seg6_local_lwt { 44d1df6fd8SDavid Lebrun int action; 45d1df6fd8SDavid Lebrun struct ipv6_sr_hdr *srh; 46d1df6fd8SDavid Lebrun int table; 47d1df6fd8SDavid Lebrun struct in_addr nh4; 48d1df6fd8SDavid Lebrun struct in6_addr nh6; 49d1df6fd8SDavid Lebrun int iif; 50d1df6fd8SDavid Lebrun int oif; 51d1df6fd8SDavid Lebrun 52d1df6fd8SDavid Lebrun int headroom; 53d1df6fd8SDavid Lebrun struct seg6_action_desc *desc; 54d1df6fd8SDavid Lebrun }; 55d1df6fd8SDavid Lebrun 56d1df6fd8SDavid Lebrun static struct seg6_local_lwt *seg6_local_lwtunnel(struct lwtunnel_state *lwt) 57d1df6fd8SDavid Lebrun { 58d1df6fd8SDavid Lebrun return (struct seg6_local_lwt *)lwt->data; 59d1df6fd8SDavid Lebrun } 60d1df6fd8SDavid Lebrun 61140f04c3SDavid Lebrun static struct ipv6_sr_hdr *get_srh(struct sk_buff *skb) 62140f04c3SDavid Lebrun { 63140f04c3SDavid Lebrun struct ipv6_sr_hdr *srh; 64140f04c3SDavid Lebrun struct ipv6hdr *hdr; 65140f04c3SDavid Lebrun int len; 66140f04c3SDavid Lebrun 67140f04c3SDavid Lebrun hdr = ipv6_hdr(skb); 68140f04c3SDavid Lebrun if (hdr->nexthdr != IPPROTO_ROUTING) 69140f04c3SDavid Lebrun return NULL; 70140f04c3SDavid Lebrun 71140f04c3SDavid Lebrun srh = (struct ipv6_sr_hdr *)(hdr + 1); 72140f04c3SDavid Lebrun len = (srh->hdrlen + 1) << 3; 73140f04c3SDavid Lebrun 74140f04c3SDavid Lebrun if (!pskb_may_pull(skb, sizeof(*hdr) + len)) 75140f04c3SDavid Lebrun return NULL; 76140f04c3SDavid Lebrun 77140f04c3SDavid Lebrun if (!seg6_validate_srh(srh, len)) 78140f04c3SDavid Lebrun return NULL; 79140f04c3SDavid Lebrun 80140f04c3SDavid Lebrun return srh; 81140f04c3SDavid Lebrun } 82140f04c3SDavid Lebrun 83140f04c3SDavid Lebrun static struct ipv6_sr_hdr *get_and_validate_srh(struct sk_buff *skb) 84140f04c3SDavid Lebrun { 85140f04c3SDavid Lebrun struct ipv6_sr_hdr *srh; 86140f04c3SDavid Lebrun 87140f04c3SDavid Lebrun srh = get_srh(skb); 88140f04c3SDavid Lebrun if (!srh) 89140f04c3SDavid Lebrun return NULL; 90140f04c3SDavid Lebrun 91140f04c3SDavid Lebrun if (srh->segments_left == 0) 92140f04c3SDavid Lebrun return NULL; 93140f04c3SDavid Lebrun 94140f04c3SDavid Lebrun #ifdef CONFIG_IPV6_SEG6_HMAC 95140f04c3SDavid Lebrun if (!seg6_hmac_validate_skb(skb)) 96140f04c3SDavid Lebrun return NULL; 97140f04c3SDavid Lebrun #endif 98140f04c3SDavid Lebrun 99140f04c3SDavid Lebrun return srh; 100140f04c3SDavid Lebrun } 101140f04c3SDavid Lebrun 102140f04c3SDavid Lebrun /* regular endpoint function */ 103140f04c3SDavid Lebrun static int input_action_end(struct sk_buff *skb, struct seg6_local_lwt *slwt) 104140f04c3SDavid Lebrun { 105140f04c3SDavid Lebrun struct ipv6_sr_hdr *srh; 106140f04c3SDavid Lebrun struct in6_addr *addr; 107140f04c3SDavid Lebrun 108140f04c3SDavid Lebrun srh = get_and_validate_srh(skb); 109140f04c3SDavid Lebrun if (!srh) 110140f04c3SDavid Lebrun goto drop; 111140f04c3SDavid Lebrun 112140f04c3SDavid Lebrun srh->segments_left--; 113140f04c3SDavid Lebrun addr = srh->segments + srh->segments_left; 114140f04c3SDavid Lebrun 115140f04c3SDavid Lebrun ipv6_hdr(skb)->daddr = *addr; 116140f04c3SDavid Lebrun 117140f04c3SDavid Lebrun skb_dst_drop(skb); 118140f04c3SDavid Lebrun ip6_route_input(skb); 119140f04c3SDavid Lebrun 120140f04c3SDavid Lebrun return dst_input(skb); 121140f04c3SDavid Lebrun 122140f04c3SDavid Lebrun drop: 123140f04c3SDavid Lebrun kfree_skb(skb); 124140f04c3SDavid Lebrun return -EINVAL; 125140f04c3SDavid Lebrun } 126140f04c3SDavid Lebrun 127140f04c3SDavid Lebrun /* regular endpoint, and forward to specified nexthop */ 128140f04c3SDavid Lebrun static int input_action_end_x(struct sk_buff *skb, struct seg6_local_lwt *slwt) 129140f04c3SDavid Lebrun { 130140f04c3SDavid Lebrun struct net *net = dev_net(skb->dev); 131140f04c3SDavid Lebrun struct ipv6_sr_hdr *srh; 132140f04c3SDavid Lebrun struct dst_entry *dst; 133140f04c3SDavid Lebrun struct in6_addr *addr; 134140f04c3SDavid Lebrun struct ipv6hdr *hdr; 135140f04c3SDavid Lebrun struct flowi6 fl6; 136140f04c3SDavid Lebrun int flags; 137140f04c3SDavid Lebrun 138140f04c3SDavid Lebrun srh = get_and_validate_srh(skb); 139140f04c3SDavid Lebrun if (!srh) 140140f04c3SDavid Lebrun goto drop; 141140f04c3SDavid Lebrun 142140f04c3SDavid Lebrun srh->segments_left--; 143140f04c3SDavid Lebrun addr = srh->segments + srh->segments_left; 144140f04c3SDavid Lebrun 145140f04c3SDavid Lebrun hdr = ipv6_hdr(skb); 146140f04c3SDavid Lebrun hdr->daddr = *addr; 147140f04c3SDavid Lebrun 148140f04c3SDavid Lebrun skb_dst_drop(skb); 149140f04c3SDavid Lebrun 150140f04c3SDavid Lebrun fl6.flowi6_iif = skb->dev->ifindex; 151140f04c3SDavid Lebrun fl6.daddr = slwt->nh6; 152140f04c3SDavid Lebrun fl6.saddr = hdr->saddr; 153140f04c3SDavid Lebrun fl6.flowlabel = ip6_flowinfo(hdr); 154140f04c3SDavid Lebrun fl6.flowi6_mark = skb->mark; 155140f04c3SDavid Lebrun fl6.flowi6_proto = hdr->nexthdr; 156140f04c3SDavid Lebrun 157140f04c3SDavid Lebrun flags = RT6_LOOKUP_F_HAS_SADDR | RT6_LOOKUP_F_IFACE | 158140f04c3SDavid Lebrun RT6_LOOKUP_F_REACHABLE; 159140f04c3SDavid Lebrun 160140f04c3SDavid Lebrun dst = ip6_route_input_lookup(net, skb->dev, &fl6, flags); 161140f04c3SDavid Lebrun if (dst->dev->flags & IFF_LOOPBACK) 162140f04c3SDavid Lebrun goto drop; 163140f04c3SDavid Lebrun 164140f04c3SDavid Lebrun skb_dst_set(skb, dst); 165140f04c3SDavid Lebrun 166140f04c3SDavid Lebrun return dst_input(skb); 167140f04c3SDavid Lebrun 168140f04c3SDavid Lebrun drop: 169140f04c3SDavid Lebrun kfree_skb(skb); 170140f04c3SDavid Lebrun return -EINVAL; 171140f04c3SDavid Lebrun } 172140f04c3SDavid Lebrun 173140f04c3SDavid Lebrun /* decapsulate and forward to specified nexthop */ 174140f04c3SDavid Lebrun static int input_action_end_dx6(struct sk_buff *skb, 175140f04c3SDavid Lebrun struct seg6_local_lwt *slwt) 176140f04c3SDavid Lebrun { 177140f04c3SDavid Lebrun struct net *net = dev_net(skb->dev); 178140f04c3SDavid Lebrun struct ipv6hdr *inner_hdr; 179140f04c3SDavid Lebrun struct ipv6_sr_hdr *srh; 180140f04c3SDavid Lebrun struct dst_entry *dst; 181140f04c3SDavid Lebrun unsigned int off = 0; 182140f04c3SDavid Lebrun struct flowi6 fl6; 183140f04c3SDavid Lebrun bool use_nh; 184140f04c3SDavid Lebrun int flags; 185140f04c3SDavid Lebrun 186140f04c3SDavid Lebrun /* this function accepts IPv6 encapsulated packets, with either 187140f04c3SDavid Lebrun * an SRH with SL=0, or no SRH. 188140f04c3SDavid Lebrun */ 189140f04c3SDavid Lebrun 190140f04c3SDavid Lebrun srh = get_srh(skb); 191140f04c3SDavid Lebrun if (srh && srh->segments_left > 0) 192140f04c3SDavid Lebrun goto drop; 193140f04c3SDavid Lebrun 194140f04c3SDavid Lebrun #ifdef CONFIG_IPV6_SEG6_HMAC 195140f04c3SDavid Lebrun if (srh && !seg6_hmac_validate_skb(skb)) 196140f04c3SDavid Lebrun goto drop; 197140f04c3SDavid Lebrun #endif 198140f04c3SDavid Lebrun 199140f04c3SDavid Lebrun if (ipv6_find_hdr(skb, &off, IPPROTO_IPV6, NULL, NULL) < 0) 200140f04c3SDavid Lebrun goto drop; 201140f04c3SDavid Lebrun 202140f04c3SDavid Lebrun if (!pskb_pull(skb, off)) 203140f04c3SDavid Lebrun goto drop; 204140f04c3SDavid Lebrun 205140f04c3SDavid Lebrun skb_postpull_rcsum(skb, skb_network_header(skb), off); 206140f04c3SDavid Lebrun 207140f04c3SDavid Lebrun skb_reset_network_header(skb); 208140f04c3SDavid Lebrun skb_reset_transport_header(skb); 209140f04c3SDavid Lebrun skb->encapsulation = 0; 210140f04c3SDavid Lebrun 211140f04c3SDavid Lebrun inner_hdr = ipv6_hdr(skb); 212140f04c3SDavid Lebrun 213140f04c3SDavid Lebrun /* The inner packet is not associated to any local interface, 214140f04c3SDavid Lebrun * so we do not call netif_rx(). 215140f04c3SDavid Lebrun * 216140f04c3SDavid Lebrun * If slwt->nh6 is set to ::, then lookup the nexthop for the 217140f04c3SDavid Lebrun * inner packet's DA. Otherwise, use the specified nexthop. 218140f04c3SDavid Lebrun */ 219140f04c3SDavid Lebrun 220140f04c3SDavid Lebrun use_nh = !ipv6_addr_any(&slwt->nh6); 221140f04c3SDavid Lebrun 222140f04c3SDavid Lebrun skb_dst_drop(skb); 223140f04c3SDavid Lebrun 224140f04c3SDavid Lebrun fl6.flowi6_iif = skb->dev->ifindex; 225140f04c3SDavid Lebrun fl6.daddr = use_nh ? slwt->nh6 : inner_hdr->daddr; 226140f04c3SDavid Lebrun fl6.saddr = inner_hdr->saddr; 227140f04c3SDavid Lebrun fl6.flowlabel = ip6_flowinfo(inner_hdr); 228140f04c3SDavid Lebrun fl6.flowi6_mark = skb->mark; 229140f04c3SDavid Lebrun fl6.flowi6_proto = inner_hdr->nexthdr; 230140f04c3SDavid Lebrun 231140f04c3SDavid Lebrun flags = RT6_LOOKUP_F_HAS_SADDR | RT6_LOOKUP_F_REACHABLE; 232140f04c3SDavid Lebrun if (use_nh) 233140f04c3SDavid Lebrun flags |= RT6_LOOKUP_F_IFACE; 234140f04c3SDavid Lebrun 235140f04c3SDavid Lebrun dst = ip6_route_input_lookup(net, skb->dev, &fl6, flags); 236140f04c3SDavid Lebrun if (dst->dev->flags & IFF_LOOPBACK) 237140f04c3SDavid Lebrun goto drop; 238140f04c3SDavid Lebrun 239140f04c3SDavid Lebrun skb_dst_set(skb, dst); 240140f04c3SDavid Lebrun 241140f04c3SDavid Lebrun return dst_input(skb); 242140f04c3SDavid Lebrun drop: 243140f04c3SDavid Lebrun kfree_skb(skb); 244140f04c3SDavid Lebrun return -EINVAL; 245140f04c3SDavid Lebrun } 246140f04c3SDavid Lebrun 247140f04c3SDavid Lebrun /* push an SRH on top of the current one */ 248140f04c3SDavid Lebrun static int input_action_end_b6(struct sk_buff *skb, struct seg6_local_lwt *slwt) 249140f04c3SDavid Lebrun { 250140f04c3SDavid Lebrun struct ipv6_sr_hdr *srh; 251140f04c3SDavid Lebrun int err = -EINVAL; 252140f04c3SDavid Lebrun 253140f04c3SDavid Lebrun srh = get_and_validate_srh(skb); 254140f04c3SDavid Lebrun if (!srh) 255140f04c3SDavid Lebrun goto drop; 256140f04c3SDavid Lebrun 257140f04c3SDavid Lebrun err = seg6_do_srh_inline(skb, slwt->srh); 258140f04c3SDavid Lebrun if (err) 259140f04c3SDavid Lebrun goto drop; 260140f04c3SDavid Lebrun 261140f04c3SDavid Lebrun ipv6_hdr(skb)->payload_len = htons(skb->len - sizeof(struct ipv6hdr)); 262140f04c3SDavid Lebrun skb_set_transport_header(skb, sizeof(struct ipv6hdr)); 263140f04c3SDavid Lebrun 264140f04c3SDavid Lebrun skb_dst_drop(skb); 265140f04c3SDavid Lebrun ip6_route_input(skb); 266140f04c3SDavid Lebrun 267140f04c3SDavid Lebrun return dst_input(skb); 268140f04c3SDavid Lebrun 269140f04c3SDavid Lebrun drop: 270140f04c3SDavid Lebrun kfree_skb(skb); 271140f04c3SDavid Lebrun return err; 272140f04c3SDavid Lebrun } 273140f04c3SDavid Lebrun 274140f04c3SDavid Lebrun /* encapsulate within an outer IPv6 header and a specified SRH */ 275140f04c3SDavid Lebrun static int input_action_end_b6_encap(struct sk_buff *skb, 276140f04c3SDavid Lebrun struct seg6_local_lwt *slwt) 277140f04c3SDavid Lebrun { 278140f04c3SDavid Lebrun struct ipv6_sr_hdr *srh; 279140f04c3SDavid Lebrun struct in6_addr *addr; 280140f04c3SDavid Lebrun int err = -EINVAL; 281140f04c3SDavid Lebrun 282140f04c3SDavid Lebrun srh = get_and_validate_srh(skb); 283140f04c3SDavid Lebrun if (!srh) 284140f04c3SDavid Lebrun goto drop; 285140f04c3SDavid Lebrun 286140f04c3SDavid Lebrun srh->segments_left--; 287140f04c3SDavid Lebrun addr = srh->segments + srh->segments_left; 288140f04c3SDavid Lebrun ipv6_hdr(skb)->daddr = *addr; 289140f04c3SDavid Lebrun 290140f04c3SDavid Lebrun skb_reset_inner_headers(skb); 291140f04c3SDavid Lebrun skb->encapsulation = 1; 292140f04c3SDavid Lebrun 29332d99d0bSDavid Lebrun err = seg6_do_srh_encap(skb, slwt->srh, IPPROTO_IPV6); 294140f04c3SDavid Lebrun if (err) 295140f04c3SDavid Lebrun goto drop; 296140f04c3SDavid Lebrun 297140f04c3SDavid Lebrun ipv6_hdr(skb)->payload_len = htons(skb->len - sizeof(struct ipv6hdr)); 298140f04c3SDavid Lebrun skb_set_transport_header(skb, sizeof(struct ipv6hdr)); 299140f04c3SDavid Lebrun 300140f04c3SDavid Lebrun skb_dst_drop(skb); 301140f04c3SDavid Lebrun ip6_route_input(skb); 302140f04c3SDavid Lebrun 303140f04c3SDavid Lebrun return dst_input(skb); 304140f04c3SDavid Lebrun 305140f04c3SDavid Lebrun drop: 306140f04c3SDavid Lebrun kfree_skb(skb); 307140f04c3SDavid Lebrun return err; 308140f04c3SDavid Lebrun } 309140f04c3SDavid Lebrun 310d1df6fd8SDavid Lebrun static struct seg6_action_desc seg6_action_table[] = { 311d1df6fd8SDavid Lebrun { 312d1df6fd8SDavid Lebrun .action = SEG6_LOCAL_ACTION_END, 313d1df6fd8SDavid Lebrun .attrs = 0, 314140f04c3SDavid Lebrun .input = input_action_end, 315d1df6fd8SDavid Lebrun }, 316140f04c3SDavid Lebrun { 317140f04c3SDavid Lebrun .action = SEG6_LOCAL_ACTION_END_X, 318140f04c3SDavid Lebrun .attrs = (1 << SEG6_LOCAL_NH6), 319140f04c3SDavid Lebrun .input = input_action_end_x, 320140f04c3SDavid Lebrun }, 321140f04c3SDavid Lebrun { 322140f04c3SDavid Lebrun .action = SEG6_LOCAL_ACTION_END_DX6, 323140f04c3SDavid Lebrun .attrs = (1 << SEG6_LOCAL_NH6), 324140f04c3SDavid Lebrun .input = input_action_end_dx6, 325140f04c3SDavid Lebrun }, 326140f04c3SDavid Lebrun { 327140f04c3SDavid Lebrun .action = SEG6_LOCAL_ACTION_END_B6, 328140f04c3SDavid Lebrun .attrs = (1 << SEG6_LOCAL_SRH), 329140f04c3SDavid Lebrun .input = input_action_end_b6, 330140f04c3SDavid Lebrun }, 331140f04c3SDavid Lebrun { 332140f04c3SDavid Lebrun .action = SEG6_LOCAL_ACTION_END_B6_ENCAP, 333140f04c3SDavid Lebrun .attrs = (1 << SEG6_LOCAL_SRH), 334140f04c3SDavid Lebrun .input = input_action_end_b6_encap, 335140f04c3SDavid Lebrun .static_headroom = sizeof(struct ipv6hdr), 336140f04c3SDavid Lebrun } 337d1df6fd8SDavid Lebrun }; 338d1df6fd8SDavid Lebrun 339d1df6fd8SDavid Lebrun static struct seg6_action_desc *__get_action_desc(int action) 340d1df6fd8SDavid Lebrun { 341d1df6fd8SDavid Lebrun struct seg6_action_desc *desc; 342d1df6fd8SDavid Lebrun int i, count; 343d1df6fd8SDavid Lebrun 344d1df6fd8SDavid Lebrun count = sizeof(seg6_action_table) / sizeof(struct seg6_action_desc); 345d1df6fd8SDavid Lebrun for (i = 0; i < count; i++) { 346d1df6fd8SDavid Lebrun desc = &seg6_action_table[i]; 347d1df6fd8SDavid Lebrun if (desc->action == action) 348d1df6fd8SDavid Lebrun return desc; 349d1df6fd8SDavid Lebrun } 350d1df6fd8SDavid Lebrun 351d1df6fd8SDavid Lebrun return NULL; 352d1df6fd8SDavid Lebrun } 353d1df6fd8SDavid Lebrun 354d1df6fd8SDavid Lebrun static int seg6_local_input(struct sk_buff *skb) 355d1df6fd8SDavid Lebrun { 356d1df6fd8SDavid Lebrun struct dst_entry *orig_dst = skb_dst(skb); 357d1df6fd8SDavid Lebrun struct seg6_action_desc *desc; 358d1df6fd8SDavid Lebrun struct seg6_local_lwt *slwt; 359d1df6fd8SDavid Lebrun 360*6285217fSDavid Lebrun if (skb->protocol != htons(ETH_P_IPV6)) { 361*6285217fSDavid Lebrun kfree_skb(skb); 362*6285217fSDavid Lebrun return -EINVAL; 363*6285217fSDavid Lebrun } 364*6285217fSDavid Lebrun 365d1df6fd8SDavid Lebrun slwt = seg6_local_lwtunnel(orig_dst->lwtstate); 366d1df6fd8SDavid Lebrun desc = slwt->desc; 367d1df6fd8SDavid Lebrun 368d1df6fd8SDavid Lebrun return desc->input(skb, slwt); 369d1df6fd8SDavid Lebrun } 370d1df6fd8SDavid Lebrun 371d1df6fd8SDavid Lebrun static const struct nla_policy seg6_local_policy[SEG6_LOCAL_MAX + 1] = { 372d1df6fd8SDavid Lebrun [SEG6_LOCAL_ACTION] = { .type = NLA_U32 }, 373d1df6fd8SDavid Lebrun [SEG6_LOCAL_SRH] = { .type = NLA_BINARY }, 374d1df6fd8SDavid Lebrun [SEG6_LOCAL_TABLE] = { .type = NLA_U32 }, 375d1df6fd8SDavid Lebrun [SEG6_LOCAL_NH4] = { .type = NLA_BINARY, 376d1df6fd8SDavid Lebrun .len = sizeof(struct in_addr) }, 377d1df6fd8SDavid Lebrun [SEG6_LOCAL_NH6] = { .type = NLA_BINARY, 378d1df6fd8SDavid Lebrun .len = sizeof(struct in6_addr) }, 379d1df6fd8SDavid Lebrun [SEG6_LOCAL_IIF] = { .type = NLA_U32 }, 380d1df6fd8SDavid Lebrun [SEG6_LOCAL_OIF] = { .type = NLA_U32 }, 381d1df6fd8SDavid Lebrun }; 382d1df6fd8SDavid Lebrun 3832d9cc60aSDavid Lebrun static int parse_nla_srh(struct nlattr **attrs, struct seg6_local_lwt *slwt) 3842d9cc60aSDavid Lebrun { 3852d9cc60aSDavid Lebrun struct ipv6_sr_hdr *srh; 3862d9cc60aSDavid Lebrun int len; 3872d9cc60aSDavid Lebrun 3882d9cc60aSDavid Lebrun srh = nla_data(attrs[SEG6_LOCAL_SRH]); 3892d9cc60aSDavid Lebrun len = nla_len(attrs[SEG6_LOCAL_SRH]); 3902d9cc60aSDavid Lebrun 3912d9cc60aSDavid Lebrun /* SRH must contain at least one segment */ 3922d9cc60aSDavid Lebrun if (len < sizeof(*srh) + sizeof(struct in6_addr)) 3932d9cc60aSDavid Lebrun return -EINVAL; 3942d9cc60aSDavid Lebrun 3952d9cc60aSDavid Lebrun if (!seg6_validate_srh(srh, len)) 3962d9cc60aSDavid Lebrun return -EINVAL; 3972d9cc60aSDavid Lebrun 3982d9cc60aSDavid Lebrun slwt->srh = kmalloc(len, GFP_KERNEL); 3992d9cc60aSDavid Lebrun if (!slwt->srh) 4002d9cc60aSDavid Lebrun return -ENOMEM; 4012d9cc60aSDavid Lebrun 4022d9cc60aSDavid Lebrun memcpy(slwt->srh, srh, len); 4032d9cc60aSDavid Lebrun 4042d9cc60aSDavid Lebrun slwt->headroom += len; 4052d9cc60aSDavid Lebrun 4062d9cc60aSDavid Lebrun return 0; 4072d9cc60aSDavid Lebrun } 4082d9cc60aSDavid Lebrun 4092d9cc60aSDavid Lebrun static int put_nla_srh(struct sk_buff *skb, struct seg6_local_lwt *slwt) 4102d9cc60aSDavid Lebrun { 4112d9cc60aSDavid Lebrun struct ipv6_sr_hdr *srh; 4122d9cc60aSDavid Lebrun struct nlattr *nla; 4132d9cc60aSDavid Lebrun int len; 4142d9cc60aSDavid Lebrun 4152d9cc60aSDavid Lebrun srh = slwt->srh; 4162d9cc60aSDavid Lebrun len = (srh->hdrlen + 1) << 3; 4172d9cc60aSDavid Lebrun 4182d9cc60aSDavid Lebrun nla = nla_reserve(skb, SEG6_LOCAL_SRH, len); 4192d9cc60aSDavid Lebrun if (!nla) 4202d9cc60aSDavid Lebrun return -EMSGSIZE; 4212d9cc60aSDavid Lebrun 4222d9cc60aSDavid Lebrun memcpy(nla_data(nla), srh, len); 4232d9cc60aSDavid Lebrun 4242d9cc60aSDavid Lebrun return 0; 4252d9cc60aSDavid Lebrun } 4262d9cc60aSDavid Lebrun 4272d9cc60aSDavid Lebrun static int cmp_nla_srh(struct seg6_local_lwt *a, struct seg6_local_lwt *b) 4282d9cc60aSDavid Lebrun { 4292d9cc60aSDavid Lebrun int len = (a->srh->hdrlen + 1) << 3; 4302d9cc60aSDavid Lebrun 4312d9cc60aSDavid Lebrun if (len != ((b->srh->hdrlen + 1) << 3)) 4322d9cc60aSDavid Lebrun return 1; 4332d9cc60aSDavid Lebrun 4342d9cc60aSDavid Lebrun return memcmp(a->srh, b->srh, len); 4352d9cc60aSDavid Lebrun } 4362d9cc60aSDavid Lebrun 4372d9cc60aSDavid Lebrun static int parse_nla_table(struct nlattr **attrs, struct seg6_local_lwt *slwt) 4382d9cc60aSDavid Lebrun { 4392d9cc60aSDavid Lebrun slwt->table = nla_get_u32(attrs[SEG6_LOCAL_TABLE]); 4402d9cc60aSDavid Lebrun 4412d9cc60aSDavid Lebrun return 0; 4422d9cc60aSDavid Lebrun } 4432d9cc60aSDavid Lebrun 4442d9cc60aSDavid Lebrun static int put_nla_table(struct sk_buff *skb, struct seg6_local_lwt *slwt) 4452d9cc60aSDavid Lebrun { 4462d9cc60aSDavid Lebrun if (nla_put_u32(skb, SEG6_LOCAL_TABLE, slwt->table)) 4472d9cc60aSDavid Lebrun return -EMSGSIZE; 4482d9cc60aSDavid Lebrun 4492d9cc60aSDavid Lebrun return 0; 4502d9cc60aSDavid Lebrun } 4512d9cc60aSDavid Lebrun 4522d9cc60aSDavid Lebrun static int cmp_nla_table(struct seg6_local_lwt *a, struct seg6_local_lwt *b) 4532d9cc60aSDavid Lebrun { 4542d9cc60aSDavid Lebrun if (a->table != b->table) 4552d9cc60aSDavid Lebrun return 1; 4562d9cc60aSDavid Lebrun 4572d9cc60aSDavid Lebrun return 0; 4582d9cc60aSDavid Lebrun } 4592d9cc60aSDavid Lebrun 4602d9cc60aSDavid Lebrun static int parse_nla_nh4(struct nlattr **attrs, struct seg6_local_lwt *slwt) 4612d9cc60aSDavid Lebrun { 4622d9cc60aSDavid Lebrun memcpy(&slwt->nh4, nla_data(attrs[SEG6_LOCAL_NH4]), 4632d9cc60aSDavid Lebrun sizeof(struct in_addr)); 4642d9cc60aSDavid Lebrun 4652d9cc60aSDavid Lebrun return 0; 4662d9cc60aSDavid Lebrun } 4672d9cc60aSDavid Lebrun 4682d9cc60aSDavid Lebrun static int put_nla_nh4(struct sk_buff *skb, struct seg6_local_lwt *slwt) 4692d9cc60aSDavid Lebrun { 4702d9cc60aSDavid Lebrun struct nlattr *nla; 4712d9cc60aSDavid Lebrun 4722d9cc60aSDavid Lebrun nla = nla_reserve(skb, SEG6_LOCAL_NH4, sizeof(struct in_addr)); 4732d9cc60aSDavid Lebrun if (!nla) 4742d9cc60aSDavid Lebrun return -EMSGSIZE; 4752d9cc60aSDavid Lebrun 4762d9cc60aSDavid Lebrun memcpy(nla_data(nla), &slwt->nh4, sizeof(struct in_addr)); 4772d9cc60aSDavid Lebrun 4782d9cc60aSDavid Lebrun return 0; 4792d9cc60aSDavid Lebrun } 4802d9cc60aSDavid Lebrun 4812d9cc60aSDavid Lebrun static int cmp_nla_nh4(struct seg6_local_lwt *a, struct seg6_local_lwt *b) 4822d9cc60aSDavid Lebrun { 4832d9cc60aSDavid Lebrun return memcmp(&a->nh4, &b->nh4, sizeof(struct in_addr)); 4842d9cc60aSDavid Lebrun } 4852d9cc60aSDavid Lebrun 4862d9cc60aSDavid Lebrun static int parse_nla_nh6(struct nlattr **attrs, struct seg6_local_lwt *slwt) 4872d9cc60aSDavid Lebrun { 4882d9cc60aSDavid Lebrun memcpy(&slwt->nh6, nla_data(attrs[SEG6_LOCAL_NH6]), 4892d9cc60aSDavid Lebrun sizeof(struct in6_addr)); 4902d9cc60aSDavid Lebrun 4912d9cc60aSDavid Lebrun return 0; 4922d9cc60aSDavid Lebrun } 4932d9cc60aSDavid Lebrun 4942d9cc60aSDavid Lebrun static int put_nla_nh6(struct sk_buff *skb, struct seg6_local_lwt *slwt) 4952d9cc60aSDavid Lebrun { 4962d9cc60aSDavid Lebrun struct nlattr *nla; 4972d9cc60aSDavid Lebrun 4982d9cc60aSDavid Lebrun nla = nla_reserve(skb, SEG6_LOCAL_NH6, sizeof(struct in6_addr)); 4992d9cc60aSDavid Lebrun if (!nla) 5002d9cc60aSDavid Lebrun return -EMSGSIZE; 5012d9cc60aSDavid Lebrun 5022d9cc60aSDavid Lebrun memcpy(nla_data(nla), &slwt->nh6, sizeof(struct in6_addr)); 5032d9cc60aSDavid Lebrun 5042d9cc60aSDavid Lebrun return 0; 5052d9cc60aSDavid Lebrun } 5062d9cc60aSDavid Lebrun 5072d9cc60aSDavid Lebrun static int cmp_nla_nh6(struct seg6_local_lwt *a, struct seg6_local_lwt *b) 5082d9cc60aSDavid Lebrun { 5092d9cc60aSDavid Lebrun return memcmp(&a->nh6, &b->nh6, sizeof(struct in6_addr)); 5102d9cc60aSDavid Lebrun } 5112d9cc60aSDavid Lebrun 5122d9cc60aSDavid Lebrun static int parse_nla_iif(struct nlattr **attrs, struct seg6_local_lwt *slwt) 5132d9cc60aSDavid Lebrun { 5142d9cc60aSDavid Lebrun slwt->iif = nla_get_u32(attrs[SEG6_LOCAL_IIF]); 5152d9cc60aSDavid Lebrun 5162d9cc60aSDavid Lebrun return 0; 5172d9cc60aSDavid Lebrun } 5182d9cc60aSDavid Lebrun 5192d9cc60aSDavid Lebrun static int put_nla_iif(struct sk_buff *skb, struct seg6_local_lwt *slwt) 5202d9cc60aSDavid Lebrun { 5212d9cc60aSDavid Lebrun if (nla_put_u32(skb, SEG6_LOCAL_IIF, slwt->iif)) 5222d9cc60aSDavid Lebrun return -EMSGSIZE; 5232d9cc60aSDavid Lebrun 5242d9cc60aSDavid Lebrun return 0; 5252d9cc60aSDavid Lebrun } 5262d9cc60aSDavid Lebrun 5272d9cc60aSDavid Lebrun static int cmp_nla_iif(struct seg6_local_lwt *a, struct seg6_local_lwt *b) 5282d9cc60aSDavid Lebrun { 5292d9cc60aSDavid Lebrun if (a->iif != b->iif) 5302d9cc60aSDavid Lebrun return 1; 5312d9cc60aSDavid Lebrun 5322d9cc60aSDavid Lebrun return 0; 5332d9cc60aSDavid Lebrun } 5342d9cc60aSDavid Lebrun 5352d9cc60aSDavid Lebrun static int parse_nla_oif(struct nlattr **attrs, struct seg6_local_lwt *slwt) 5362d9cc60aSDavid Lebrun { 5372d9cc60aSDavid Lebrun slwt->oif = nla_get_u32(attrs[SEG6_LOCAL_OIF]); 5382d9cc60aSDavid Lebrun 5392d9cc60aSDavid Lebrun return 0; 5402d9cc60aSDavid Lebrun } 5412d9cc60aSDavid Lebrun 5422d9cc60aSDavid Lebrun static int put_nla_oif(struct sk_buff *skb, struct seg6_local_lwt *slwt) 5432d9cc60aSDavid Lebrun { 5442d9cc60aSDavid Lebrun if (nla_put_u32(skb, SEG6_LOCAL_OIF, slwt->oif)) 5452d9cc60aSDavid Lebrun return -EMSGSIZE; 5462d9cc60aSDavid Lebrun 5472d9cc60aSDavid Lebrun return 0; 5482d9cc60aSDavid Lebrun } 5492d9cc60aSDavid Lebrun 5502d9cc60aSDavid Lebrun static int cmp_nla_oif(struct seg6_local_lwt *a, struct seg6_local_lwt *b) 5512d9cc60aSDavid Lebrun { 5522d9cc60aSDavid Lebrun if (a->oif != b->oif) 5532d9cc60aSDavid Lebrun return 1; 5542d9cc60aSDavid Lebrun 5552d9cc60aSDavid Lebrun return 0; 5562d9cc60aSDavid Lebrun } 5572d9cc60aSDavid Lebrun 558d1df6fd8SDavid Lebrun struct seg6_action_param { 559d1df6fd8SDavid Lebrun int (*parse)(struct nlattr **attrs, struct seg6_local_lwt *slwt); 560d1df6fd8SDavid Lebrun int (*put)(struct sk_buff *skb, struct seg6_local_lwt *slwt); 561d1df6fd8SDavid Lebrun int (*cmp)(struct seg6_local_lwt *a, struct seg6_local_lwt *b); 562d1df6fd8SDavid Lebrun }; 563d1df6fd8SDavid Lebrun 564d1df6fd8SDavid Lebrun static struct seg6_action_param seg6_action_params[SEG6_LOCAL_MAX + 1] = { 5652d9cc60aSDavid Lebrun [SEG6_LOCAL_SRH] = { .parse = parse_nla_srh, 5662d9cc60aSDavid Lebrun .put = put_nla_srh, 5672d9cc60aSDavid Lebrun .cmp = cmp_nla_srh }, 568d1df6fd8SDavid Lebrun 5692d9cc60aSDavid Lebrun [SEG6_LOCAL_TABLE] = { .parse = parse_nla_table, 5702d9cc60aSDavid Lebrun .put = put_nla_table, 5712d9cc60aSDavid Lebrun .cmp = cmp_nla_table }, 572d1df6fd8SDavid Lebrun 5732d9cc60aSDavid Lebrun [SEG6_LOCAL_NH4] = { .parse = parse_nla_nh4, 5742d9cc60aSDavid Lebrun .put = put_nla_nh4, 5752d9cc60aSDavid Lebrun .cmp = cmp_nla_nh4 }, 576d1df6fd8SDavid Lebrun 5772d9cc60aSDavid Lebrun [SEG6_LOCAL_NH6] = { .parse = parse_nla_nh6, 5782d9cc60aSDavid Lebrun .put = put_nla_nh6, 5792d9cc60aSDavid Lebrun .cmp = cmp_nla_nh6 }, 580d1df6fd8SDavid Lebrun 5812d9cc60aSDavid Lebrun [SEG6_LOCAL_IIF] = { .parse = parse_nla_iif, 5822d9cc60aSDavid Lebrun .put = put_nla_iif, 5832d9cc60aSDavid Lebrun .cmp = cmp_nla_iif }, 584d1df6fd8SDavid Lebrun 5852d9cc60aSDavid Lebrun [SEG6_LOCAL_OIF] = { .parse = parse_nla_oif, 5862d9cc60aSDavid Lebrun .put = put_nla_oif, 5872d9cc60aSDavid Lebrun .cmp = cmp_nla_oif }, 588d1df6fd8SDavid Lebrun }; 589d1df6fd8SDavid Lebrun 590d1df6fd8SDavid Lebrun static int parse_nla_action(struct nlattr **attrs, struct seg6_local_lwt *slwt) 591d1df6fd8SDavid Lebrun { 592d1df6fd8SDavid Lebrun struct seg6_action_param *param; 593d1df6fd8SDavid Lebrun struct seg6_action_desc *desc; 594d1df6fd8SDavid Lebrun int i, err; 595d1df6fd8SDavid Lebrun 596d1df6fd8SDavid Lebrun desc = __get_action_desc(slwt->action); 597d1df6fd8SDavid Lebrun if (!desc) 598d1df6fd8SDavid Lebrun return -EINVAL; 599d1df6fd8SDavid Lebrun 600d1df6fd8SDavid Lebrun if (!desc->input) 601d1df6fd8SDavid Lebrun return -EOPNOTSUPP; 602d1df6fd8SDavid Lebrun 603d1df6fd8SDavid Lebrun slwt->desc = desc; 604d1df6fd8SDavid Lebrun slwt->headroom += desc->static_headroom; 605d1df6fd8SDavid Lebrun 606d1df6fd8SDavid Lebrun for (i = 0; i < SEG6_LOCAL_MAX + 1; i++) { 607d1df6fd8SDavid Lebrun if (desc->attrs & (1 << i)) { 608d1df6fd8SDavid Lebrun if (!attrs[i]) 609d1df6fd8SDavid Lebrun return -EINVAL; 610d1df6fd8SDavid Lebrun 611d1df6fd8SDavid Lebrun param = &seg6_action_params[i]; 612d1df6fd8SDavid Lebrun 613d1df6fd8SDavid Lebrun err = param->parse(attrs, slwt); 614d1df6fd8SDavid Lebrun if (err < 0) 615d1df6fd8SDavid Lebrun return err; 616d1df6fd8SDavid Lebrun } 617d1df6fd8SDavid Lebrun } 618d1df6fd8SDavid Lebrun 619d1df6fd8SDavid Lebrun return 0; 620d1df6fd8SDavid Lebrun } 621d1df6fd8SDavid Lebrun 622d1df6fd8SDavid Lebrun static int seg6_local_build_state(struct nlattr *nla, unsigned int family, 623d1df6fd8SDavid Lebrun const void *cfg, struct lwtunnel_state **ts, 624d1df6fd8SDavid Lebrun struct netlink_ext_ack *extack) 625d1df6fd8SDavid Lebrun { 626d1df6fd8SDavid Lebrun struct nlattr *tb[SEG6_LOCAL_MAX + 1]; 627d1df6fd8SDavid Lebrun struct lwtunnel_state *newts; 628d1df6fd8SDavid Lebrun struct seg6_local_lwt *slwt; 629d1df6fd8SDavid Lebrun int err; 630d1df6fd8SDavid Lebrun 631*6285217fSDavid Lebrun if (family != AF_INET6) 632*6285217fSDavid Lebrun return -EINVAL; 633*6285217fSDavid Lebrun 634d1df6fd8SDavid Lebrun err = nla_parse_nested(tb, SEG6_LOCAL_MAX, nla, seg6_local_policy, 635d1df6fd8SDavid Lebrun extack); 636d1df6fd8SDavid Lebrun 637d1df6fd8SDavid Lebrun if (err < 0) 638d1df6fd8SDavid Lebrun return err; 639d1df6fd8SDavid Lebrun 640d1df6fd8SDavid Lebrun if (!tb[SEG6_LOCAL_ACTION]) 641d1df6fd8SDavid Lebrun return -EINVAL; 642d1df6fd8SDavid Lebrun 643d1df6fd8SDavid Lebrun newts = lwtunnel_state_alloc(sizeof(*slwt)); 644d1df6fd8SDavid Lebrun if (!newts) 645d1df6fd8SDavid Lebrun return -ENOMEM; 646d1df6fd8SDavid Lebrun 647d1df6fd8SDavid Lebrun slwt = seg6_local_lwtunnel(newts); 648d1df6fd8SDavid Lebrun slwt->action = nla_get_u32(tb[SEG6_LOCAL_ACTION]); 649d1df6fd8SDavid Lebrun 650d1df6fd8SDavid Lebrun err = parse_nla_action(tb, slwt); 651d1df6fd8SDavid Lebrun if (err < 0) 652d1df6fd8SDavid Lebrun goto out_free; 653d1df6fd8SDavid Lebrun 654d1df6fd8SDavid Lebrun newts->type = LWTUNNEL_ENCAP_SEG6_LOCAL; 655d1df6fd8SDavid Lebrun newts->flags = LWTUNNEL_STATE_INPUT_REDIRECT; 656d1df6fd8SDavid Lebrun newts->headroom = slwt->headroom; 657d1df6fd8SDavid Lebrun 658d1df6fd8SDavid Lebrun *ts = newts; 659d1df6fd8SDavid Lebrun 660d1df6fd8SDavid Lebrun return 0; 661d1df6fd8SDavid Lebrun 662d1df6fd8SDavid Lebrun out_free: 663d1df6fd8SDavid Lebrun kfree(slwt->srh); 664d1df6fd8SDavid Lebrun kfree(newts); 665d1df6fd8SDavid Lebrun return err; 666d1df6fd8SDavid Lebrun } 667d1df6fd8SDavid Lebrun 668d1df6fd8SDavid Lebrun static void seg6_local_destroy_state(struct lwtunnel_state *lwt) 669d1df6fd8SDavid Lebrun { 670d1df6fd8SDavid Lebrun struct seg6_local_lwt *slwt = seg6_local_lwtunnel(lwt); 671d1df6fd8SDavid Lebrun 672d1df6fd8SDavid Lebrun kfree(slwt->srh); 673d1df6fd8SDavid Lebrun } 674d1df6fd8SDavid Lebrun 675d1df6fd8SDavid Lebrun static int seg6_local_fill_encap(struct sk_buff *skb, 676d1df6fd8SDavid Lebrun struct lwtunnel_state *lwt) 677d1df6fd8SDavid Lebrun { 678d1df6fd8SDavid Lebrun struct seg6_local_lwt *slwt = seg6_local_lwtunnel(lwt); 679d1df6fd8SDavid Lebrun struct seg6_action_param *param; 680d1df6fd8SDavid Lebrun int i, err; 681d1df6fd8SDavid Lebrun 682d1df6fd8SDavid Lebrun if (nla_put_u32(skb, SEG6_LOCAL_ACTION, slwt->action)) 683d1df6fd8SDavid Lebrun return -EMSGSIZE; 684d1df6fd8SDavid Lebrun 685d1df6fd8SDavid Lebrun for (i = 0; i < SEG6_LOCAL_MAX + 1; i++) { 686d1df6fd8SDavid Lebrun if (slwt->desc->attrs & (1 << i)) { 687d1df6fd8SDavid Lebrun param = &seg6_action_params[i]; 688d1df6fd8SDavid Lebrun err = param->put(skb, slwt); 689d1df6fd8SDavid Lebrun if (err < 0) 690d1df6fd8SDavid Lebrun return err; 691d1df6fd8SDavid Lebrun } 692d1df6fd8SDavid Lebrun } 693d1df6fd8SDavid Lebrun 694d1df6fd8SDavid Lebrun return 0; 695d1df6fd8SDavid Lebrun } 696d1df6fd8SDavid Lebrun 697d1df6fd8SDavid Lebrun static int seg6_local_get_encap_size(struct lwtunnel_state *lwt) 698d1df6fd8SDavid Lebrun { 699d1df6fd8SDavid Lebrun struct seg6_local_lwt *slwt = seg6_local_lwtunnel(lwt); 700d1df6fd8SDavid Lebrun unsigned long attrs; 701d1df6fd8SDavid Lebrun int nlsize; 702d1df6fd8SDavid Lebrun 703d1df6fd8SDavid Lebrun nlsize = nla_total_size(4); /* action */ 704d1df6fd8SDavid Lebrun 705d1df6fd8SDavid Lebrun attrs = slwt->desc->attrs; 706d1df6fd8SDavid Lebrun 707d1df6fd8SDavid Lebrun if (attrs & (1 << SEG6_LOCAL_SRH)) 708d1df6fd8SDavid Lebrun nlsize += nla_total_size((slwt->srh->hdrlen + 1) << 3); 709d1df6fd8SDavid Lebrun 710d1df6fd8SDavid Lebrun if (attrs & (1 << SEG6_LOCAL_TABLE)) 711d1df6fd8SDavid Lebrun nlsize += nla_total_size(4); 712d1df6fd8SDavid Lebrun 713d1df6fd8SDavid Lebrun if (attrs & (1 << SEG6_LOCAL_NH4)) 714d1df6fd8SDavid Lebrun nlsize += nla_total_size(4); 715d1df6fd8SDavid Lebrun 716d1df6fd8SDavid Lebrun if (attrs & (1 << SEG6_LOCAL_NH6)) 717d1df6fd8SDavid Lebrun nlsize += nla_total_size(16); 718d1df6fd8SDavid Lebrun 719d1df6fd8SDavid Lebrun if (attrs & (1 << SEG6_LOCAL_IIF)) 720d1df6fd8SDavid Lebrun nlsize += nla_total_size(4); 721d1df6fd8SDavid Lebrun 722d1df6fd8SDavid Lebrun if (attrs & (1 << SEG6_LOCAL_OIF)) 723d1df6fd8SDavid Lebrun nlsize += nla_total_size(4); 724d1df6fd8SDavid Lebrun 725d1df6fd8SDavid Lebrun return nlsize; 726d1df6fd8SDavid Lebrun } 727d1df6fd8SDavid Lebrun 728d1df6fd8SDavid Lebrun static int seg6_local_cmp_encap(struct lwtunnel_state *a, 729d1df6fd8SDavid Lebrun struct lwtunnel_state *b) 730d1df6fd8SDavid Lebrun { 731d1df6fd8SDavid Lebrun struct seg6_local_lwt *slwt_a, *slwt_b; 732d1df6fd8SDavid Lebrun struct seg6_action_param *param; 733d1df6fd8SDavid Lebrun int i; 734d1df6fd8SDavid Lebrun 735d1df6fd8SDavid Lebrun slwt_a = seg6_local_lwtunnel(a); 736d1df6fd8SDavid Lebrun slwt_b = seg6_local_lwtunnel(b); 737d1df6fd8SDavid Lebrun 738d1df6fd8SDavid Lebrun if (slwt_a->action != slwt_b->action) 739d1df6fd8SDavid Lebrun return 1; 740d1df6fd8SDavid Lebrun 741d1df6fd8SDavid Lebrun if (slwt_a->desc->attrs != slwt_b->desc->attrs) 742d1df6fd8SDavid Lebrun return 1; 743d1df6fd8SDavid Lebrun 744d1df6fd8SDavid Lebrun for (i = 0; i < SEG6_LOCAL_MAX + 1; i++) { 745d1df6fd8SDavid Lebrun if (slwt_a->desc->attrs & (1 << i)) { 746d1df6fd8SDavid Lebrun param = &seg6_action_params[i]; 747d1df6fd8SDavid Lebrun if (param->cmp(slwt_a, slwt_b)) 748d1df6fd8SDavid Lebrun return 1; 749d1df6fd8SDavid Lebrun } 750d1df6fd8SDavid Lebrun } 751d1df6fd8SDavid Lebrun 752d1df6fd8SDavid Lebrun return 0; 753d1df6fd8SDavid Lebrun } 754d1df6fd8SDavid Lebrun 755d1df6fd8SDavid Lebrun static const struct lwtunnel_encap_ops seg6_local_ops = { 756d1df6fd8SDavid Lebrun .build_state = seg6_local_build_state, 757d1df6fd8SDavid Lebrun .destroy_state = seg6_local_destroy_state, 758d1df6fd8SDavid Lebrun .input = seg6_local_input, 759d1df6fd8SDavid Lebrun .fill_encap = seg6_local_fill_encap, 760d1df6fd8SDavid Lebrun .get_encap_size = seg6_local_get_encap_size, 761d1df6fd8SDavid Lebrun .cmp_encap = seg6_local_cmp_encap, 762d1df6fd8SDavid Lebrun .owner = THIS_MODULE, 763d1df6fd8SDavid Lebrun }; 764d1df6fd8SDavid Lebrun 765d1df6fd8SDavid Lebrun int __init seg6_local_init(void) 766d1df6fd8SDavid Lebrun { 767d1df6fd8SDavid Lebrun return lwtunnel_encap_add_ops(&seg6_local_ops, 768d1df6fd8SDavid Lebrun LWTUNNEL_ENCAP_SEG6_LOCAL); 769d1df6fd8SDavid Lebrun } 770d1df6fd8SDavid Lebrun 771d1df6fd8SDavid Lebrun void seg6_local_exit(void) 772d1df6fd8SDavid Lebrun { 773d1df6fd8SDavid Lebrun lwtunnel_encap_del_ops(&seg6_local_ops, LWTUNNEL_ENCAP_SEG6_LOCAL); 774d1df6fd8SDavid Lebrun } 775