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 102*d7a669ddSDavid Lebrun static bool decap_and_validate(struct sk_buff *skb, int proto) 103*d7a669ddSDavid Lebrun { 104*d7a669ddSDavid Lebrun struct ipv6_sr_hdr *srh; 105*d7a669ddSDavid Lebrun unsigned int off = 0; 106*d7a669ddSDavid Lebrun 107*d7a669ddSDavid Lebrun srh = get_srh(skb); 108*d7a669ddSDavid Lebrun if (srh && srh->segments_left > 0) 109*d7a669ddSDavid Lebrun return false; 110*d7a669ddSDavid Lebrun 111*d7a669ddSDavid Lebrun #ifdef CONFIG_IPV6_SEG6_HMAC 112*d7a669ddSDavid Lebrun if (srh && !seg6_hmac_validate_skb(skb)) 113*d7a669ddSDavid Lebrun return false; 114*d7a669ddSDavid Lebrun #endif 115*d7a669ddSDavid Lebrun 116*d7a669ddSDavid Lebrun if (ipv6_find_hdr(skb, &off, proto, NULL, NULL) < 0) 117*d7a669ddSDavid Lebrun return false; 118*d7a669ddSDavid Lebrun 119*d7a669ddSDavid Lebrun if (!pskb_pull(skb, off)) 120*d7a669ddSDavid Lebrun return false; 121*d7a669ddSDavid Lebrun 122*d7a669ddSDavid Lebrun skb_postpull_rcsum(skb, skb_network_header(skb), off); 123*d7a669ddSDavid Lebrun 124*d7a669ddSDavid Lebrun skb_reset_network_header(skb); 125*d7a669ddSDavid Lebrun skb_reset_transport_header(skb); 126*d7a669ddSDavid Lebrun skb->encapsulation = 0; 127*d7a669ddSDavid Lebrun 128*d7a669ddSDavid Lebrun return true; 129*d7a669ddSDavid Lebrun } 130*d7a669ddSDavid Lebrun 131*d7a669ddSDavid Lebrun static void advance_nextseg(struct ipv6_sr_hdr *srh, struct in6_addr *daddr) 132*d7a669ddSDavid Lebrun { 133*d7a669ddSDavid Lebrun struct in6_addr *addr; 134*d7a669ddSDavid Lebrun 135*d7a669ddSDavid Lebrun srh->segments_left--; 136*d7a669ddSDavid Lebrun addr = srh->segments + srh->segments_left; 137*d7a669ddSDavid Lebrun *daddr = *addr; 138*d7a669ddSDavid Lebrun } 139*d7a669ddSDavid Lebrun 140*d7a669ddSDavid Lebrun static void lookup_nexthop(struct sk_buff *skb, struct in6_addr *nhaddr, 141*d7a669ddSDavid Lebrun u32 tbl_id) 142*d7a669ddSDavid Lebrun { 143*d7a669ddSDavid Lebrun struct net *net = dev_net(skb->dev); 144*d7a669ddSDavid Lebrun struct ipv6hdr *hdr = ipv6_hdr(skb); 145*d7a669ddSDavid Lebrun int flags = RT6_LOOKUP_F_HAS_SADDR; 146*d7a669ddSDavid Lebrun struct dst_entry *dst = NULL; 147*d7a669ddSDavid Lebrun struct rt6_info *rt; 148*d7a669ddSDavid Lebrun struct flowi6 fl6; 149*d7a669ddSDavid Lebrun 150*d7a669ddSDavid Lebrun fl6.flowi6_iif = skb->dev->ifindex; 151*d7a669ddSDavid Lebrun fl6.daddr = nhaddr ? *nhaddr : hdr->daddr; 152*d7a669ddSDavid Lebrun fl6.saddr = hdr->saddr; 153*d7a669ddSDavid Lebrun fl6.flowlabel = ip6_flowinfo(hdr); 154*d7a669ddSDavid Lebrun fl6.flowi6_mark = skb->mark; 155*d7a669ddSDavid Lebrun fl6.flowi6_proto = hdr->nexthdr; 156*d7a669ddSDavid Lebrun 157*d7a669ddSDavid Lebrun if (nhaddr) 158*d7a669ddSDavid Lebrun fl6.flowi6_flags = FLOWI_FLAG_KNOWN_NH; 159*d7a669ddSDavid Lebrun 160*d7a669ddSDavid Lebrun if (!tbl_id) { 161*d7a669ddSDavid Lebrun dst = ip6_route_input_lookup(net, skb->dev, &fl6, flags); 162*d7a669ddSDavid Lebrun } else { 163*d7a669ddSDavid Lebrun struct fib6_table *table; 164*d7a669ddSDavid Lebrun 165*d7a669ddSDavid Lebrun table = fib6_get_table(net, tbl_id); 166*d7a669ddSDavid Lebrun if (!table) 167*d7a669ddSDavid Lebrun goto out; 168*d7a669ddSDavid Lebrun 169*d7a669ddSDavid Lebrun rt = ip6_pol_route(net, table, 0, &fl6, flags); 170*d7a669ddSDavid Lebrun dst = &rt->dst; 171*d7a669ddSDavid Lebrun } 172*d7a669ddSDavid Lebrun 173*d7a669ddSDavid Lebrun if (dst && dst->dev->flags & IFF_LOOPBACK && !dst->error) { 174*d7a669ddSDavid Lebrun dst_release(dst); 175*d7a669ddSDavid Lebrun dst = NULL; 176*d7a669ddSDavid Lebrun } 177*d7a669ddSDavid Lebrun 178*d7a669ddSDavid Lebrun out: 179*d7a669ddSDavid Lebrun if (!dst) { 180*d7a669ddSDavid Lebrun rt = net->ipv6.ip6_blk_hole_entry; 181*d7a669ddSDavid Lebrun dst = &rt->dst; 182*d7a669ddSDavid Lebrun dst_hold(dst); 183*d7a669ddSDavid Lebrun } 184*d7a669ddSDavid Lebrun 185*d7a669ddSDavid Lebrun skb_dst_drop(skb); 186*d7a669ddSDavid Lebrun skb_dst_set(skb, dst); 187*d7a669ddSDavid Lebrun } 188*d7a669ddSDavid Lebrun 189140f04c3SDavid Lebrun /* regular endpoint function */ 190140f04c3SDavid Lebrun static int input_action_end(struct sk_buff *skb, struct seg6_local_lwt *slwt) 191140f04c3SDavid Lebrun { 192140f04c3SDavid Lebrun struct ipv6_sr_hdr *srh; 193140f04c3SDavid Lebrun 194140f04c3SDavid Lebrun srh = get_and_validate_srh(skb); 195140f04c3SDavid Lebrun if (!srh) 196140f04c3SDavid Lebrun goto drop; 197140f04c3SDavid Lebrun 198*d7a669ddSDavid Lebrun advance_nextseg(srh, &ipv6_hdr(skb)->daddr); 199140f04c3SDavid Lebrun 200*d7a669ddSDavid Lebrun lookup_nexthop(skb, NULL, 0); 201140f04c3SDavid Lebrun 202140f04c3SDavid Lebrun return dst_input(skb); 203140f04c3SDavid Lebrun 204140f04c3SDavid Lebrun drop: 205140f04c3SDavid Lebrun kfree_skb(skb); 206140f04c3SDavid Lebrun return -EINVAL; 207140f04c3SDavid Lebrun } 208140f04c3SDavid Lebrun 209140f04c3SDavid Lebrun /* regular endpoint, and forward to specified nexthop */ 210140f04c3SDavid Lebrun static int input_action_end_x(struct sk_buff *skb, struct seg6_local_lwt *slwt) 211140f04c3SDavid Lebrun { 212140f04c3SDavid Lebrun struct ipv6_sr_hdr *srh; 213140f04c3SDavid Lebrun 214140f04c3SDavid Lebrun srh = get_and_validate_srh(skb); 215140f04c3SDavid Lebrun if (!srh) 216140f04c3SDavid Lebrun goto drop; 217140f04c3SDavid Lebrun 218*d7a669ddSDavid Lebrun advance_nextseg(srh, &ipv6_hdr(skb)->daddr); 219140f04c3SDavid Lebrun 220*d7a669ddSDavid Lebrun lookup_nexthop(skb, &slwt->nh6, 0); 221140f04c3SDavid Lebrun 222140f04c3SDavid Lebrun return dst_input(skb); 223140f04c3SDavid Lebrun 224140f04c3SDavid Lebrun drop: 225140f04c3SDavid Lebrun kfree_skb(skb); 226140f04c3SDavid Lebrun return -EINVAL; 227140f04c3SDavid Lebrun } 228140f04c3SDavid Lebrun 229140f04c3SDavid Lebrun /* decapsulate and forward to specified nexthop */ 230140f04c3SDavid Lebrun static int input_action_end_dx6(struct sk_buff *skb, 231140f04c3SDavid Lebrun struct seg6_local_lwt *slwt) 232140f04c3SDavid Lebrun { 233*d7a669ddSDavid Lebrun struct in6_addr *nhaddr = NULL; 234140f04c3SDavid Lebrun 235140f04c3SDavid Lebrun /* this function accepts IPv6 encapsulated packets, with either 236140f04c3SDavid Lebrun * an SRH with SL=0, or no SRH. 237140f04c3SDavid Lebrun */ 238140f04c3SDavid Lebrun 239*d7a669ddSDavid Lebrun if (!decap_and_validate(skb, IPPROTO_IPV6)) 240140f04c3SDavid Lebrun goto drop; 241140f04c3SDavid Lebrun 242*d7a669ddSDavid Lebrun if (!pskb_may_pull(skb, sizeof(struct ipv6hdr))) 243140f04c3SDavid Lebrun goto drop; 244140f04c3SDavid Lebrun 245140f04c3SDavid Lebrun /* The inner packet is not associated to any local interface, 246140f04c3SDavid Lebrun * so we do not call netif_rx(). 247140f04c3SDavid Lebrun * 248140f04c3SDavid Lebrun * If slwt->nh6 is set to ::, then lookup the nexthop for the 249140f04c3SDavid Lebrun * inner packet's DA. Otherwise, use the specified nexthop. 250140f04c3SDavid Lebrun */ 251140f04c3SDavid Lebrun 252*d7a669ddSDavid Lebrun if (!ipv6_addr_any(&slwt->nh6)) 253*d7a669ddSDavid Lebrun nhaddr = &slwt->nh6; 254140f04c3SDavid Lebrun 255*d7a669ddSDavid Lebrun lookup_nexthop(skb, nhaddr, 0); 256140f04c3SDavid Lebrun 257140f04c3SDavid Lebrun return dst_input(skb); 258140f04c3SDavid Lebrun drop: 259140f04c3SDavid Lebrun kfree_skb(skb); 260140f04c3SDavid Lebrun return -EINVAL; 261140f04c3SDavid Lebrun } 262140f04c3SDavid Lebrun 263140f04c3SDavid Lebrun /* push an SRH on top of the current one */ 264140f04c3SDavid Lebrun static int input_action_end_b6(struct sk_buff *skb, struct seg6_local_lwt *slwt) 265140f04c3SDavid Lebrun { 266140f04c3SDavid Lebrun struct ipv6_sr_hdr *srh; 267140f04c3SDavid Lebrun int err = -EINVAL; 268140f04c3SDavid Lebrun 269140f04c3SDavid Lebrun srh = get_and_validate_srh(skb); 270140f04c3SDavid Lebrun if (!srh) 271140f04c3SDavid Lebrun goto drop; 272140f04c3SDavid Lebrun 273140f04c3SDavid Lebrun err = seg6_do_srh_inline(skb, slwt->srh); 274140f04c3SDavid Lebrun if (err) 275140f04c3SDavid Lebrun goto drop; 276140f04c3SDavid Lebrun 277140f04c3SDavid Lebrun ipv6_hdr(skb)->payload_len = htons(skb->len - sizeof(struct ipv6hdr)); 278140f04c3SDavid Lebrun skb_set_transport_header(skb, sizeof(struct ipv6hdr)); 279140f04c3SDavid Lebrun 280*d7a669ddSDavid Lebrun lookup_nexthop(skb, NULL, 0); 281140f04c3SDavid Lebrun 282140f04c3SDavid Lebrun return dst_input(skb); 283140f04c3SDavid Lebrun 284140f04c3SDavid Lebrun drop: 285140f04c3SDavid Lebrun kfree_skb(skb); 286140f04c3SDavid Lebrun return err; 287140f04c3SDavid Lebrun } 288140f04c3SDavid Lebrun 289140f04c3SDavid Lebrun /* encapsulate within an outer IPv6 header and a specified SRH */ 290140f04c3SDavid Lebrun static int input_action_end_b6_encap(struct sk_buff *skb, 291140f04c3SDavid Lebrun struct seg6_local_lwt *slwt) 292140f04c3SDavid Lebrun { 293140f04c3SDavid Lebrun struct ipv6_sr_hdr *srh; 294140f04c3SDavid Lebrun int err = -EINVAL; 295140f04c3SDavid Lebrun 296140f04c3SDavid Lebrun srh = get_and_validate_srh(skb); 297140f04c3SDavid Lebrun if (!srh) 298140f04c3SDavid Lebrun goto drop; 299140f04c3SDavid Lebrun 300*d7a669ddSDavid Lebrun advance_nextseg(srh, &ipv6_hdr(skb)->daddr); 301140f04c3SDavid Lebrun 302140f04c3SDavid Lebrun skb_reset_inner_headers(skb); 303140f04c3SDavid Lebrun skb->encapsulation = 1; 304140f04c3SDavid Lebrun 30532d99d0bSDavid Lebrun err = seg6_do_srh_encap(skb, slwt->srh, IPPROTO_IPV6); 306140f04c3SDavid Lebrun if (err) 307140f04c3SDavid Lebrun goto drop; 308140f04c3SDavid Lebrun 309140f04c3SDavid Lebrun ipv6_hdr(skb)->payload_len = htons(skb->len - sizeof(struct ipv6hdr)); 310140f04c3SDavid Lebrun skb_set_transport_header(skb, sizeof(struct ipv6hdr)); 311140f04c3SDavid Lebrun 312*d7a669ddSDavid Lebrun lookup_nexthop(skb, NULL, 0); 313140f04c3SDavid Lebrun 314140f04c3SDavid Lebrun return dst_input(skb); 315140f04c3SDavid Lebrun 316140f04c3SDavid Lebrun drop: 317140f04c3SDavid Lebrun kfree_skb(skb); 318140f04c3SDavid Lebrun return err; 319140f04c3SDavid Lebrun } 320140f04c3SDavid Lebrun 321d1df6fd8SDavid Lebrun static struct seg6_action_desc seg6_action_table[] = { 322d1df6fd8SDavid Lebrun { 323d1df6fd8SDavid Lebrun .action = SEG6_LOCAL_ACTION_END, 324d1df6fd8SDavid Lebrun .attrs = 0, 325140f04c3SDavid Lebrun .input = input_action_end, 326d1df6fd8SDavid Lebrun }, 327140f04c3SDavid Lebrun { 328140f04c3SDavid Lebrun .action = SEG6_LOCAL_ACTION_END_X, 329140f04c3SDavid Lebrun .attrs = (1 << SEG6_LOCAL_NH6), 330140f04c3SDavid Lebrun .input = input_action_end_x, 331140f04c3SDavid Lebrun }, 332140f04c3SDavid Lebrun { 333140f04c3SDavid Lebrun .action = SEG6_LOCAL_ACTION_END_DX6, 334140f04c3SDavid Lebrun .attrs = (1 << SEG6_LOCAL_NH6), 335140f04c3SDavid Lebrun .input = input_action_end_dx6, 336140f04c3SDavid Lebrun }, 337140f04c3SDavid Lebrun { 338140f04c3SDavid Lebrun .action = SEG6_LOCAL_ACTION_END_B6, 339140f04c3SDavid Lebrun .attrs = (1 << SEG6_LOCAL_SRH), 340140f04c3SDavid Lebrun .input = input_action_end_b6, 341140f04c3SDavid Lebrun }, 342140f04c3SDavid Lebrun { 343140f04c3SDavid Lebrun .action = SEG6_LOCAL_ACTION_END_B6_ENCAP, 344140f04c3SDavid Lebrun .attrs = (1 << SEG6_LOCAL_SRH), 345140f04c3SDavid Lebrun .input = input_action_end_b6_encap, 346140f04c3SDavid Lebrun .static_headroom = sizeof(struct ipv6hdr), 347140f04c3SDavid Lebrun } 348d1df6fd8SDavid Lebrun }; 349d1df6fd8SDavid Lebrun 350d1df6fd8SDavid Lebrun static struct seg6_action_desc *__get_action_desc(int action) 351d1df6fd8SDavid Lebrun { 352d1df6fd8SDavid Lebrun struct seg6_action_desc *desc; 353d1df6fd8SDavid Lebrun int i, count; 354d1df6fd8SDavid Lebrun 355d1df6fd8SDavid Lebrun count = sizeof(seg6_action_table) / sizeof(struct seg6_action_desc); 356d1df6fd8SDavid Lebrun for (i = 0; i < count; i++) { 357d1df6fd8SDavid Lebrun desc = &seg6_action_table[i]; 358d1df6fd8SDavid Lebrun if (desc->action == action) 359d1df6fd8SDavid Lebrun return desc; 360d1df6fd8SDavid Lebrun } 361d1df6fd8SDavid Lebrun 362d1df6fd8SDavid Lebrun return NULL; 363d1df6fd8SDavid Lebrun } 364d1df6fd8SDavid Lebrun 365d1df6fd8SDavid Lebrun static int seg6_local_input(struct sk_buff *skb) 366d1df6fd8SDavid Lebrun { 367d1df6fd8SDavid Lebrun struct dst_entry *orig_dst = skb_dst(skb); 368d1df6fd8SDavid Lebrun struct seg6_action_desc *desc; 369d1df6fd8SDavid Lebrun struct seg6_local_lwt *slwt; 370d1df6fd8SDavid Lebrun 3716285217fSDavid Lebrun if (skb->protocol != htons(ETH_P_IPV6)) { 3726285217fSDavid Lebrun kfree_skb(skb); 3736285217fSDavid Lebrun return -EINVAL; 3746285217fSDavid Lebrun } 3756285217fSDavid Lebrun 376d1df6fd8SDavid Lebrun slwt = seg6_local_lwtunnel(orig_dst->lwtstate); 377d1df6fd8SDavid Lebrun desc = slwt->desc; 378d1df6fd8SDavid Lebrun 379d1df6fd8SDavid Lebrun return desc->input(skb, slwt); 380d1df6fd8SDavid Lebrun } 381d1df6fd8SDavid Lebrun 382d1df6fd8SDavid Lebrun static const struct nla_policy seg6_local_policy[SEG6_LOCAL_MAX + 1] = { 383d1df6fd8SDavid Lebrun [SEG6_LOCAL_ACTION] = { .type = NLA_U32 }, 384d1df6fd8SDavid Lebrun [SEG6_LOCAL_SRH] = { .type = NLA_BINARY }, 385d1df6fd8SDavid Lebrun [SEG6_LOCAL_TABLE] = { .type = NLA_U32 }, 386d1df6fd8SDavid Lebrun [SEG6_LOCAL_NH4] = { .type = NLA_BINARY, 387d1df6fd8SDavid Lebrun .len = sizeof(struct in_addr) }, 388d1df6fd8SDavid Lebrun [SEG6_LOCAL_NH6] = { .type = NLA_BINARY, 389d1df6fd8SDavid Lebrun .len = sizeof(struct in6_addr) }, 390d1df6fd8SDavid Lebrun [SEG6_LOCAL_IIF] = { .type = NLA_U32 }, 391d1df6fd8SDavid Lebrun [SEG6_LOCAL_OIF] = { .type = NLA_U32 }, 392d1df6fd8SDavid Lebrun }; 393d1df6fd8SDavid Lebrun 3942d9cc60aSDavid Lebrun static int parse_nla_srh(struct nlattr **attrs, struct seg6_local_lwt *slwt) 3952d9cc60aSDavid Lebrun { 3962d9cc60aSDavid Lebrun struct ipv6_sr_hdr *srh; 3972d9cc60aSDavid Lebrun int len; 3982d9cc60aSDavid Lebrun 3992d9cc60aSDavid Lebrun srh = nla_data(attrs[SEG6_LOCAL_SRH]); 4002d9cc60aSDavid Lebrun len = nla_len(attrs[SEG6_LOCAL_SRH]); 4012d9cc60aSDavid Lebrun 4022d9cc60aSDavid Lebrun /* SRH must contain at least one segment */ 4032d9cc60aSDavid Lebrun if (len < sizeof(*srh) + sizeof(struct in6_addr)) 4042d9cc60aSDavid Lebrun return -EINVAL; 4052d9cc60aSDavid Lebrun 4062d9cc60aSDavid Lebrun if (!seg6_validate_srh(srh, len)) 4072d9cc60aSDavid Lebrun return -EINVAL; 4082d9cc60aSDavid Lebrun 4092d9cc60aSDavid Lebrun slwt->srh = kmalloc(len, GFP_KERNEL); 4102d9cc60aSDavid Lebrun if (!slwt->srh) 4112d9cc60aSDavid Lebrun return -ENOMEM; 4122d9cc60aSDavid Lebrun 4132d9cc60aSDavid Lebrun memcpy(slwt->srh, srh, len); 4142d9cc60aSDavid Lebrun 4152d9cc60aSDavid Lebrun slwt->headroom += len; 4162d9cc60aSDavid Lebrun 4172d9cc60aSDavid Lebrun return 0; 4182d9cc60aSDavid Lebrun } 4192d9cc60aSDavid Lebrun 4202d9cc60aSDavid Lebrun static int put_nla_srh(struct sk_buff *skb, struct seg6_local_lwt *slwt) 4212d9cc60aSDavid Lebrun { 4222d9cc60aSDavid Lebrun struct ipv6_sr_hdr *srh; 4232d9cc60aSDavid Lebrun struct nlattr *nla; 4242d9cc60aSDavid Lebrun int len; 4252d9cc60aSDavid Lebrun 4262d9cc60aSDavid Lebrun srh = slwt->srh; 4272d9cc60aSDavid Lebrun len = (srh->hdrlen + 1) << 3; 4282d9cc60aSDavid Lebrun 4292d9cc60aSDavid Lebrun nla = nla_reserve(skb, SEG6_LOCAL_SRH, len); 4302d9cc60aSDavid Lebrun if (!nla) 4312d9cc60aSDavid Lebrun return -EMSGSIZE; 4322d9cc60aSDavid Lebrun 4332d9cc60aSDavid Lebrun memcpy(nla_data(nla), srh, len); 4342d9cc60aSDavid Lebrun 4352d9cc60aSDavid Lebrun return 0; 4362d9cc60aSDavid Lebrun } 4372d9cc60aSDavid Lebrun 4382d9cc60aSDavid Lebrun static int cmp_nla_srh(struct seg6_local_lwt *a, struct seg6_local_lwt *b) 4392d9cc60aSDavid Lebrun { 4402d9cc60aSDavid Lebrun int len = (a->srh->hdrlen + 1) << 3; 4412d9cc60aSDavid Lebrun 4422d9cc60aSDavid Lebrun if (len != ((b->srh->hdrlen + 1) << 3)) 4432d9cc60aSDavid Lebrun return 1; 4442d9cc60aSDavid Lebrun 4452d9cc60aSDavid Lebrun return memcmp(a->srh, b->srh, len); 4462d9cc60aSDavid Lebrun } 4472d9cc60aSDavid Lebrun 4482d9cc60aSDavid Lebrun static int parse_nla_table(struct nlattr **attrs, struct seg6_local_lwt *slwt) 4492d9cc60aSDavid Lebrun { 4502d9cc60aSDavid Lebrun slwt->table = nla_get_u32(attrs[SEG6_LOCAL_TABLE]); 4512d9cc60aSDavid Lebrun 4522d9cc60aSDavid Lebrun return 0; 4532d9cc60aSDavid Lebrun } 4542d9cc60aSDavid Lebrun 4552d9cc60aSDavid Lebrun static int put_nla_table(struct sk_buff *skb, struct seg6_local_lwt *slwt) 4562d9cc60aSDavid Lebrun { 4572d9cc60aSDavid Lebrun if (nla_put_u32(skb, SEG6_LOCAL_TABLE, slwt->table)) 4582d9cc60aSDavid Lebrun return -EMSGSIZE; 4592d9cc60aSDavid Lebrun 4602d9cc60aSDavid Lebrun return 0; 4612d9cc60aSDavid Lebrun } 4622d9cc60aSDavid Lebrun 4632d9cc60aSDavid Lebrun static int cmp_nla_table(struct seg6_local_lwt *a, struct seg6_local_lwt *b) 4642d9cc60aSDavid Lebrun { 4652d9cc60aSDavid Lebrun if (a->table != b->table) 4662d9cc60aSDavid Lebrun return 1; 4672d9cc60aSDavid Lebrun 4682d9cc60aSDavid Lebrun return 0; 4692d9cc60aSDavid Lebrun } 4702d9cc60aSDavid Lebrun 4712d9cc60aSDavid Lebrun static int parse_nla_nh4(struct nlattr **attrs, struct seg6_local_lwt *slwt) 4722d9cc60aSDavid Lebrun { 4732d9cc60aSDavid Lebrun memcpy(&slwt->nh4, nla_data(attrs[SEG6_LOCAL_NH4]), 4742d9cc60aSDavid Lebrun sizeof(struct in_addr)); 4752d9cc60aSDavid Lebrun 4762d9cc60aSDavid Lebrun return 0; 4772d9cc60aSDavid Lebrun } 4782d9cc60aSDavid Lebrun 4792d9cc60aSDavid Lebrun static int put_nla_nh4(struct sk_buff *skb, struct seg6_local_lwt *slwt) 4802d9cc60aSDavid Lebrun { 4812d9cc60aSDavid Lebrun struct nlattr *nla; 4822d9cc60aSDavid Lebrun 4832d9cc60aSDavid Lebrun nla = nla_reserve(skb, SEG6_LOCAL_NH4, sizeof(struct in_addr)); 4842d9cc60aSDavid Lebrun if (!nla) 4852d9cc60aSDavid Lebrun return -EMSGSIZE; 4862d9cc60aSDavid Lebrun 4872d9cc60aSDavid Lebrun memcpy(nla_data(nla), &slwt->nh4, sizeof(struct in_addr)); 4882d9cc60aSDavid Lebrun 4892d9cc60aSDavid Lebrun return 0; 4902d9cc60aSDavid Lebrun } 4912d9cc60aSDavid Lebrun 4922d9cc60aSDavid Lebrun static int cmp_nla_nh4(struct seg6_local_lwt *a, struct seg6_local_lwt *b) 4932d9cc60aSDavid Lebrun { 4942d9cc60aSDavid Lebrun return memcmp(&a->nh4, &b->nh4, sizeof(struct in_addr)); 4952d9cc60aSDavid Lebrun } 4962d9cc60aSDavid Lebrun 4972d9cc60aSDavid Lebrun static int parse_nla_nh6(struct nlattr **attrs, struct seg6_local_lwt *slwt) 4982d9cc60aSDavid Lebrun { 4992d9cc60aSDavid Lebrun memcpy(&slwt->nh6, nla_data(attrs[SEG6_LOCAL_NH6]), 5002d9cc60aSDavid Lebrun sizeof(struct in6_addr)); 5012d9cc60aSDavid Lebrun 5022d9cc60aSDavid Lebrun return 0; 5032d9cc60aSDavid Lebrun } 5042d9cc60aSDavid Lebrun 5052d9cc60aSDavid Lebrun static int put_nla_nh6(struct sk_buff *skb, struct seg6_local_lwt *slwt) 5062d9cc60aSDavid Lebrun { 5072d9cc60aSDavid Lebrun struct nlattr *nla; 5082d9cc60aSDavid Lebrun 5092d9cc60aSDavid Lebrun nla = nla_reserve(skb, SEG6_LOCAL_NH6, sizeof(struct in6_addr)); 5102d9cc60aSDavid Lebrun if (!nla) 5112d9cc60aSDavid Lebrun return -EMSGSIZE; 5122d9cc60aSDavid Lebrun 5132d9cc60aSDavid Lebrun memcpy(nla_data(nla), &slwt->nh6, sizeof(struct in6_addr)); 5142d9cc60aSDavid Lebrun 5152d9cc60aSDavid Lebrun return 0; 5162d9cc60aSDavid Lebrun } 5172d9cc60aSDavid Lebrun 5182d9cc60aSDavid Lebrun static int cmp_nla_nh6(struct seg6_local_lwt *a, struct seg6_local_lwt *b) 5192d9cc60aSDavid Lebrun { 5202d9cc60aSDavid Lebrun return memcmp(&a->nh6, &b->nh6, sizeof(struct in6_addr)); 5212d9cc60aSDavid Lebrun } 5222d9cc60aSDavid Lebrun 5232d9cc60aSDavid Lebrun static int parse_nla_iif(struct nlattr **attrs, struct seg6_local_lwt *slwt) 5242d9cc60aSDavid Lebrun { 5252d9cc60aSDavid Lebrun slwt->iif = nla_get_u32(attrs[SEG6_LOCAL_IIF]); 5262d9cc60aSDavid Lebrun 5272d9cc60aSDavid Lebrun return 0; 5282d9cc60aSDavid Lebrun } 5292d9cc60aSDavid Lebrun 5302d9cc60aSDavid Lebrun static int put_nla_iif(struct sk_buff *skb, struct seg6_local_lwt *slwt) 5312d9cc60aSDavid Lebrun { 5322d9cc60aSDavid Lebrun if (nla_put_u32(skb, SEG6_LOCAL_IIF, slwt->iif)) 5332d9cc60aSDavid Lebrun return -EMSGSIZE; 5342d9cc60aSDavid Lebrun 5352d9cc60aSDavid Lebrun return 0; 5362d9cc60aSDavid Lebrun } 5372d9cc60aSDavid Lebrun 5382d9cc60aSDavid Lebrun static int cmp_nla_iif(struct seg6_local_lwt *a, struct seg6_local_lwt *b) 5392d9cc60aSDavid Lebrun { 5402d9cc60aSDavid Lebrun if (a->iif != b->iif) 5412d9cc60aSDavid Lebrun return 1; 5422d9cc60aSDavid Lebrun 5432d9cc60aSDavid Lebrun return 0; 5442d9cc60aSDavid Lebrun } 5452d9cc60aSDavid Lebrun 5462d9cc60aSDavid Lebrun static int parse_nla_oif(struct nlattr **attrs, struct seg6_local_lwt *slwt) 5472d9cc60aSDavid Lebrun { 5482d9cc60aSDavid Lebrun slwt->oif = nla_get_u32(attrs[SEG6_LOCAL_OIF]); 5492d9cc60aSDavid Lebrun 5502d9cc60aSDavid Lebrun return 0; 5512d9cc60aSDavid Lebrun } 5522d9cc60aSDavid Lebrun 5532d9cc60aSDavid Lebrun static int put_nla_oif(struct sk_buff *skb, struct seg6_local_lwt *slwt) 5542d9cc60aSDavid Lebrun { 5552d9cc60aSDavid Lebrun if (nla_put_u32(skb, SEG6_LOCAL_OIF, slwt->oif)) 5562d9cc60aSDavid Lebrun return -EMSGSIZE; 5572d9cc60aSDavid Lebrun 5582d9cc60aSDavid Lebrun return 0; 5592d9cc60aSDavid Lebrun } 5602d9cc60aSDavid Lebrun 5612d9cc60aSDavid Lebrun static int cmp_nla_oif(struct seg6_local_lwt *a, struct seg6_local_lwt *b) 5622d9cc60aSDavid Lebrun { 5632d9cc60aSDavid Lebrun if (a->oif != b->oif) 5642d9cc60aSDavid Lebrun return 1; 5652d9cc60aSDavid Lebrun 5662d9cc60aSDavid Lebrun return 0; 5672d9cc60aSDavid Lebrun } 5682d9cc60aSDavid Lebrun 569d1df6fd8SDavid Lebrun struct seg6_action_param { 570d1df6fd8SDavid Lebrun int (*parse)(struct nlattr **attrs, struct seg6_local_lwt *slwt); 571d1df6fd8SDavid Lebrun int (*put)(struct sk_buff *skb, struct seg6_local_lwt *slwt); 572d1df6fd8SDavid Lebrun int (*cmp)(struct seg6_local_lwt *a, struct seg6_local_lwt *b); 573d1df6fd8SDavid Lebrun }; 574d1df6fd8SDavid Lebrun 575d1df6fd8SDavid Lebrun static struct seg6_action_param seg6_action_params[SEG6_LOCAL_MAX + 1] = { 5762d9cc60aSDavid Lebrun [SEG6_LOCAL_SRH] = { .parse = parse_nla_srh, 5772d9cc60aSDavid Lebrun .put = put_nla_srh, 5782d9cc60aSDavid Lebrun .cmp = cmp_nla_srh }, 579d1df6fd8SDavid Lebrun 5802d9cc60aSDavid Lebrun [SEG6_LOCAL_TABLE] = { .parse = parse_nla_table, 5812d9cc60aSDavid Lebrun .put = put_nla_table, 5822d9cc60aSDavid Lebrun .cmp = cmp_nla_table }, 583d1df6fd8SDavid Lebrun 5842d9cc60aSDavid Lebrun [SEG6_LOCAL_NH4] = { .parse = parse_nla_nh4, 5852d9cc60aSDavid Lebrun .put = put_nla_nh4, 5862d9cc60aSDavid Lebrun .cmp = cmp_nla_nh4 }, 587d1df6fd8SDavid Lebrun 5882d9cc60aSDavid Lebrun [SEG6_LOCAL_NH6] = { .parse = parse_nla_nh6, 5892d9cc60aSDavid Lebrun .put = put_nla_nh6, 5902d9cc60aSDavid Lebrun .cmp = cmp_nla_nh6 }, 591d1df6fd8SDavid Lebrun 5922d9cc60aSDavid Lebrun [SEG6_LOCAL_IIF] = { .parse = parse_nla_iif, 5932d9cc60aSDavid Lebrun .put = put_nla_iif, 5942d9cc60aSDavid Lebrun .cmp = cmp_nla_iif }, 595d1df6fd8SDavid Lebrun 5962d9cc60aSDavid Lebrun [SEG6_LOCAL_OIF] = { .parse = parse_nla_oif, 5972d9cc60aSDavid Lebrun .put = put_nla_oif, 5982d9cc60aSDavid Lebrun .cmp = cmp_nla_oif }, 599d1df6fd8SDavid Lebrun }; 600d1df6fd8SDavid Lebrun 601d1df6fd8SDavid Lebrun static int parse_nla_action(struct nlattr **attrs, struct seg6_local_lwt *slwt) 602d1df6fd8SDavid Lebrun { 603d1df6fd8SDavid Lebrun struct seg6_action_param *param; 604d1df6fd8SDavid Lebrun struct seg6_action_desc *desc; 605d1df6fd8SDavid Lebrun int i, err; 606d1df6fd8SDavid Lebrun 607d1df6fd8SDavid Lebrun desc = __get_action_desc(slwt->action); 608d1df6fd8SDavid Lebrun if (!desc) 609d1df6fd8SDavid Lebrun return -EINVAL; 610d1df6fd8SDavid Lebrun 611d1df6fd8SDavid Lebrun if (!desc->input) 612d1df6fd8SDavid Lebrun return -EOPNOTSUPP; 613d1df6fd8SDavid Lebrun 614d1df6fd8SDavid Lebrun slwt->desc = desc; 615d1df6fd8SDavid Lebrun slwt->headroom += desc->static_headroom; 616d1df6fd8SDavid Lebrun 617d1df6fd8SDavid Lebrun for (i = 0; i < SEG6_LOCAL_MAX + 1; i++) { 618d1df6fd8SDavid Lebrun if (desc->attrs & (1 << i)) { 619d1df6fd8SDavid Lebrun if (!attrs[i]) 620d1df6fd8SDavid Lebrun return -EINVAL; 621d1df6fd8SDavid Lebrun 622d1df6fd8SDavid Lebrun param = &seg6_action_params[i]; 623d1df6fd8SDavid Lebrun 624d1df6fd8SDavid Lebrun err = param->parse(attrs, slwt); 625d1df6fd8SDavid Lebrun if (err < 0) 626d1df6fd8SDavid Lebrun return err; 627d1df6fd8SDavid Lebrun } 628d1df6fd8SDavid Lebrun } 629d1df6fd8SDavid Lebrun 630d1df6fd8SDavid Lebrun return 0; 631d1df6fd8SDavid Lebrun } 632d1df6fd8SDavid Lebrun 633d1df6fd8SDavid Lebrun static int seg6_local_build_state(struct nlattr *nla, unsigned int family, 634d1df6fd8SDavid Lebrun const void *cfg, struct lwtunnel_state **ts, 635d1df6fd8SDavid Lebrun struct netlink_ext_ack *extack) 636d1df6fd8SDavid Lebrun { 637d1df6fd8SDavid Lebrun struct nlattr *tb[SEG6_LOCAL_MAX + 1]; 638d1df6fd8SDavid Lebrun struct lwtunnel_state *newts; 639d1df6fd8SDavid Lebrun struct seg6_local_lwt *slwt; 640d1df6fd8SDavid Lebrun int err; 641d1df6fd8SDavid Lebrun 6426285217fSDavid Lebrun if (family != AF_INET6) 6436285217fSDavid Lebrun return -EINVAL; 6446285217fSDavid Lebrun 645d1df6fd8SDavid Lebrun err = nla_parse_nested(tb, SEG6_LOCAL_MAX, nla, seg6_local_policy, 646d1df6fd8SDavid Lebrun extack); 647d1df6fd8SDavid Lebrun 648d1df6fd8SDavid Lebrun if (err < 0) 649d1df6fd8SDavid Lebrun return err; 650d1df6fd8SDavid Lebrun 651d1df6fd8SDavid Lebrun if (!tb[SEG6_LOCAL_ACTION]) 652d1df6fd8SDavid Lebrun return -EINVAL; 653d1df6fd8SDavid Lebrun 654d1df6fd8SDavid Lebrun newts = lwtunnel_state_alloc(sizeof(*slwt)); 655d1df6fd8SDavid Lebrun if (!newts) 656d1df6fd8SDavid Lebrun return -ENOMEM; 657d1df6fd8SDavid Lebrun 658d1df6fd8SDavid Lebrun slwt = seg6_local_lwtunnel(newts); 659d1df6fd8SDavid Lebrun slwt->action = nla_get_u32(tb[SEG6_LOCAL_ACTION]); 660d1df6fd8SDavid Lebrun 661d1df6fd8SDavid Lebrun err = parse_nla_action(tb, slwt); 662d1df6fd8SDavid Lebrun if (err < 0) 663d1df6fd8SDavid Lebrun goto out_free; 664d1df6fd8SDavid Lebrun 665d1df6fd8SDavid Lebrun newts->type = LWTUNNEL_ENCAP_SEG6_LOCAL; 666d1df6fd8SDavid Lebrun newts->flags = LWTUNNEL_STATE_INPUT_REDIRECT; 667d1df6fd8SDavid Lebrun newts->headroom = slwt->headroom; 668d1df6fd8SDavid Lebrun 669d1df6fd8SDavid Lebrun *ts = newts; 670d1df6fd8SDavid Lebrun 671d1df6fd8SDavid Lebrun return 0; 672d1df6fd8SDavid Lebrun 673d1df6fd8SDavid Lebrun out_free: 674d1df6fd8SDavid Lebrun kfree(slwt->srh); 675d1df6fd8SDavid Lebrun kfree(newts); 676d1df6fd8SDavid Lebrun return err; 677d1df6fd8SDavid Lebrun } 678d1df6fd8SDavid Lebrun 679d1df6fd8SDavid Lebrun static void seg6_local_destroy_state(struct lwtunnel_state *lwt) 680d1df6fd8SDavid Lebrun { 681d1df6fd8SDavid Lebrun struct seg6_local_lwt *slwt = seg6_local_lwtunnel(lwt); 682d1df6fd8SDavid Lebrun 683d1df6fd8SDavid Lebrun kfree(slwt->srh); 684d1df6fd8SDavid Lebrun } 685d1df6fd8SDavid Lebrun 686d1df6fd8SDavid Lebrun static int seg6_local_fill_encap(struct sk_buff *skb, 687d1df6fd8SDavid Lebrun struct lwtunnel_state *lwt) 688d1df6fd8SDavid Lebrun { 689d1df6fd8SDavid Lebrun struct seg6_local_lwt *slwt = seg6_local_lwtunnel(lwt); 690d1df6fd8SDavid Lebrun struct seg6_action_param *param; 691d1df6fd8SDavid Lebrun int i, err; 692d1df6fd8SDavid Lebrun 693d1df6fd8SDavid Lebrun if (nla_put_u32(skb, SEG6_LOCAL_ACTION, slwt->action)) 694d1df6fd8SDavid Lebrun return -EMSGSIZE; 695d1df6fd8SDavid Lebrun 696d1df6fd8SDavid Lebrun for (i = 0; i < SEG6_LOCAL_MAX + 1; i++) { 697d1df6fd8SDavid Lebrun if (slwt->desc->attrs & (1 << i)) { 698d1df6fd8SDavid Lebrun param = &seg6_action_params[i]; 699d1df6fd8SDavid Lebrun err = param->put(skb, slwt); 700d1df6fd8SDavid Lebrun if (err < 0) 701d1df6fd8SDavid Lebrun return err; 702d1df6fd8SDavid Lebrun } 703d1df6fd8SDavid Lebrun } 704d1df6fd8SDavid Lebrun 705d1df6fd8SDavid Lebrun return 0; 706d1df6fd8SDavid Lebrun } 707d1df6fd8SDavid Lebrun 708d1df6fd8SDavid Lebrun static int seg6_local_get_encap_size(struct lwtunnel_state *lwt) 709d1df6fd8SDavid Lebrun { 710d1df6fd8SDavid Lebrun struct seg6_local_lwt *slwt = seg6_local_lwtunnel(lwt); 711d1df6fd8SDavid Lebrun unsigned long attrs; 712d1df6fd8SDavid Lebrun int nlsize; 713d1df6fd8SDavid Lebrun 714d1df6fd8SDavid Lebrun nlsize = nla_total_size(4); /* action */ 715d1df6fd8SDavid Lebrun 716d1df6fd8SDavid Lebrun attrs = slwt->desc->attrs; 717d1df6fd8SDavid Lebrun 718d1df6fd8SDavid Lebrun if (attrs & (1 << SEG6_LOCAL_SRH)) 719d1df6fd8SDavid Lebrun nlsize += nla_total_size((slwt->srh->hdrlen + 1) << 3); 720d1df6fd8SDavid Lebrun 721d1df6fd8SDavid Lebrun if (attrs & (1 << SEG6_LOCAL_TABLE)) 722d1df6fd8SDavid Lebrun nlsize += nla_total_size(4); 723d1df6fd8SDavid Lebrun 724d1df6fd8SDavid Lebrun if (attrs & (1 << SEG6_LOCAL_NH4)) 725d1df6fd8SDavid Lebrun nlsize += nla_total_size(4); 726d1df6fd8SDavid Lebrun 727d1df6fd8SDavid Lebrun if (attrs & (1 << SEG6_LOCAL_NH6)) 728d1df6fd8SDavid Lebrun nlsize += nla_total_size(16); 729d1df6fd8SDavid Lebrun 730d1df6fd8SDavid Lebrun if (attrs & (1 << SEG6_LOCAL_IIF)) 731d1df6fd8SDavid Lebrun nlsize += nla_total_size(4); 732d1df6fd8SDavid Lebrun 733d1df6fd8SDavid Lebrun if (attrs & (1 << SEG6_LOCAL_OIF)) 734d1df6fd8SDavid Lebrun nlsize += nla_total_size(4); 735d1df6fd8SDavid Lebrun 736d1df6fd8SDavid Lebrun return nlsize; 737d1df6fd8SDavid Lebrun } 738d1df6fd8SDavid Lebrun 739d1df6fd8SDavid Lebrun static int seg6_local_cmp_encap(struct lwtunnel_state *a, 740d1df6fd8SDavid Lebrun struct lwtunnel_state *b) 741d1df6fd8SDavid Lebrun { 742d1df6fd8SDavid Lebrun struct seg6_local_lwt *slwt_a, *slwt_b; 743d1df6fd8SDavid Lebrun struct seg6_action_param *param; 744d1df6fd8SDavid Lebrun int i; 745d1df6fd8SDavid Lebrun 746d1df6fd8SDavid Lebrun slwt_a = seg6_local_lwtunnel(a); 747d1df6fd8SDavid Lebrun slwt_b = seg6_local_lwtunnel(b); 748d1df6fd8SDavid Lebrun 749d1df6fd8SDavid Lebrun if (slwt_a->action != slwt_b->action) 750d1df6fd8SDavid Lebrun return 1; 751d1df6fd8SDavid Lebrun 752d1df6fd8SDavid Lebrun if (slwt_a->desc->attrs != slwt_b->desc->attrs) 753d1df6fd8SDavid Lebrun return 1; 754d1df6fd8SDavid Lebrun 755d1df6fd8SDavid Lebrun for (i = 0; i < SEG6_LOCAL_MAX + 1; i++) { 756d1df6fd8SDavid Lebrun if (slwt_a->desc->attrs & (1 << i)) { 757d1df6fd8SDavid Lebrun param = &seg6_action_params[i]; 758d1df6fd8SDavid Lebrun if (param->cmp(slwt_a, slwt_b)) 759d1df6fd8SDavid Lebrun return 1; 760d1df6fd8SDavid Lebrun } 761d1df6fd8SDavid Lebrun } 762d1df6fd8SDavid Lebrun 763d1df6fd8SDavid Lebrun return 0; 764d1df6fd8SDavid Lebrun } 765d1df6fd8SDavid Lebrun 766d1df6fd8SDavid Lebrun static const struct lwtunnel_encap_ops seg6_local_ops = { 767d1df6fd8SDavid Lebrun .build_state = seg6_local_build_state, 768d1df6fd8SDavid Lebrun .destroy_state = seg6_local_destroy_state, 769d1df6fd8SDavid Lebrun .input = seg6_local_input, 770d1df6fd8SDavid Lebrun .fill_encap = seg6_local_fill_encap, 771d1df6fd8SDavid Lebrun .get_encap_size = seg6_local_get_encap_size, 772d1df6fd8SDavid Lebrun .cmp_encap = seg6_local_cmp_encap, 773d1df6fd8SDavid Lebrun .owner = THIS_MODULE, 774d1df6fd8SDavid Lebrun }; 775d1df6fd8SDavid Lebrun 776d1df6fd8SDavid Lebrun int __init seg6_local_init(void) 777d1df6fd8SDavid Lebrun { 778d1df6fd8SDavid Lebrun return lwtunnel_encap_add_ops(&seg6_local_ops, 779d1df6fd8SDavid Lebrun LWTUNNEL_ENCAP_SEG6_LOCAL); 780d1df6fd8SDavid Lebrun } 781d1df6fd8SDavid Lebrun 782d1df6fd8SDavid Lebrun void seg6_local_exit(void) 783d1df6fd8SDavid Lebrun { 784d1df6fd8SDavid Lebrun lwtunnel_encap_del_ops(&seg6_local_ops, LWTUNNEL_ENCAP_SEG6_LOCAL); 785d1df6fd8SDavid Lebrun } 786