1d1df6fd8SDavid Lebrun /* 2d1df6fd8SDavid Lebrun * SR-IPv6 implementation 3d1df6fd8SDavid Lebrun * 4004d4b27SMathieu Xhonneux * Authors: 5d1df6fd8SDavid Lebrun * David Lebrun <david.lebrun@uclouvain.be> 6004d4b27SMathieu Xhonneux * eBPF support: Mathieu Xhonneux <m.xhonneux@gmail.com> 7d1df6fd8SDavid Lebrun * 8d1df6fd8SDavid Lebrun * 9d1df6fd8SDavid Lebrun * This program is free software; you can redistribute it and/or 10d1df6fd8SDavid Lebrun * modify it under the terms of the GNU General Public License 11d1df6fd8SDavid Lebrun * as published by the Free Software Foundation; either version 12d1df6fd8SDavid Lebrun * 2 of the License, or (at your option) any later version. 13d1df6fd8SDavid Lebrun */ 14d1df6fd8SDavid Lebrun 15d1df6fd8SDavid Lebrun #include <linux/types.h> 16d1df6fd8SDavid Lebrun #include <linux/skbuff.h> 17d1df6fd8SDavid Lebrun #include <linux/net.h> 18d1df6fd8SDavid Lebrun #include <linux/module.h> 19d1df6fd8SDavid Lebrun #include <net/ip.h> 20d1df6fd8SDavid Lebrun #include <net/lwtunnel.h> 21d1df6fd8SDavid Lebrun #include <net/netevent.h> 22d1df6fd8SDavid Lebrun #include <net/netns/generic.h> 23d1df6fd8SDavid Lebrun #include <net/ip6_fib.h> 24d1df6fd8SDavid Lebrun #include <net/route.h> 25d1df6fd8SDavid Lebrun #include <net/seg6.h> 26d1df6fd8SDavid Lebrun #include <linux/seg6.h> 27d1df6fd8SDavid Lebrun #include <linux/seg6_local.h> 28d1df6fd8SDavid Lebrun #include <net/addrconf.h> 29d1df6fd8SDavid Lebrun #include <net/ip6_route.h> 30d1df6fd8SDavid Lebrun #include <net/dst_cache.h> 31d1df6fd8SDavid Lebrun #ifdef CONFIG_IPV6_SEG6_HMAC 32d1df6fd8SDavid Lebrun #include <net/seg6_hmac.h> 33d1df6fd8SDavid Lebrun #endif 341c1e761eSMathieu Xhonneux #include <net/seg6_local.h> 35891ef8ddSDavid Lebrun #include <linux/etherdevice.h> 36004d4b27SMathieu Xhonneux #include <linux/bpf.h> 37d1df6fd8SDavid Lebrun 38d1df6fd8SDavid Lebrun struct seg6_local_lwt; 39d1df6fd8SDavid Lebrun 40d1df6fd8SDavid Lebrun struct seg6_action_desc { 41d1df6fd8SDavid Lebrun int action; 42d1df6fd8SDavid Lebrun unsigned long attrs; 43d1df6fd8SDavid Lebrun int (*input)(struct sk_buff *skb, struct seg6_local_lwt *slwt); 44d1df6fd8SDavid Lebrun int static_headroom; 45d1df6fd8SDavid Lebrun }; 46d1df6fd8SDavid Lebrun 47004d4b27SMathieu Xhonneux struct bpf_lwt_prog { 48004d4b27SMathieu Xhonneux struct bpf_prog *prog; 49004d4b27SMathieu Xhonneux char *name; 50004d4b27SMathieu Xhonneux }; 51004d4b27SMathieu Xhonneux 52d1df6fd8SDavid Lebrun struct seg6_local_lwt { 53d1df6fd8SDavid Lebrun int action; 54d1df6fd8SDavid Lebrun struct ipv6_sr_hdr *srh; 55d1df6fd8SDavid Lebrun int table; 56d1df6fd8SDavid Lebrun struct in_addr nh4; 57d1df6fd8SDavid Lebrun struct in6_addr nh6; 58d1df6fd8SDavid Lebrun int iif; 59d1df6fd8SDavid Lebrun int oif; 60004d4b27SMathieu Xhonneux struct bpf_lwt_prog bpf; 61d1df6fd8SDavid Lebrun 62d1df6fd8SDavid Lebrun int headroom; 63d1df6fd8SDavid Lebrun struct seg6_action_desc *desc; 64d1df6fd8SDavid Lebrun }; 65d1df6fd8SDavid Lebrun 66d1df6fd8SDavid Lebrun static struct seg6_local_lwt *seg6_local_lwtunnel(struct lwtunnel_state *lwt) 67d1df6fd8SDavid Lebrun { 68d1df6fd8SDavid Lebrun return (struct seg6_local_lwt *)lwt->data; 69d1df6fd8SDavid Lebrun } 70d1df6fd8SDavid Lebrun 71140f04c3SDavid Lebrun static struct ipv6_sr_hdr *get_srh(struct sk_buff *skb) 72140f04c3SDavid Lebrun { 73140f04c3SDavid Lebrun struct ipv6_sr_hdr *srh; 745829d70bSAhmed Abdelsalam int len, srhoff = 0; 75140f04c3SDavid Lebrun 765829d70bSAhmed Abdelsalam if (ipv6_find_hdr(skb, &srhoff, IPPROTO_ROUTING, NULL, NULL) < 0) 77140f04c3SDavid Lebrun return NULL; 78140f04c3SDavid Lebrun 795829d70bSAhmed Abdelsalam if (!pskb_may_pull(skb, srhoff + sizeof(*srh))) 805829d70bSAhmed Abdelsalam return NULL; 815829d70bSAhmed Abdelsalam 825829d70bSAhmed Abdelsalam srh = (struct ipv6_sr_hdr *)(skb->data + srhoff); 835829d70bSAhmed Abdelsalam 84140f04c3SDavid Lebrun len = (srh->hdrlen + 1) << 3; 85140f04c3SDavid Lebrun 865829d70bSAhmed Abdelsalam if (!pskb_may_pull(skb, srhoff + len)) 87140f04c3SDavid Lebrun return NULL; 88140f04c3SDavid Lebrun 89140f04c3SDavid Lebrun if (!seg6_validate_srh(srh, len)) 90140f04c3SDavid Lebrun return NULL; 91140f04c3SDavid Lebrun 92140f04c3SDavid Lebrun return srh; 93140f04c3SDavid Lebrun } 94140f04c3SDavid Lebrun 95140f04c3SDavid Lebrun static struct ipv6_sr_hdr *get_and_validate_srh(struct sk_buff *skb) 96140f04c3SDavid Lebrun { 97140f04c3SDavid Lebrun struct ipv6_sr_hdr *srh; 98140f04c3SDavid Lebrun 99140f04c3SDavid Lebrun srh = get_srh(skb); 100140f04c3SDavid Lebrun if (!srh) 101140f04c3SDavid Lebrun return NULL; 102140f04c3SDavid Lebrun 103140f04c3SDavid Lebrun if (srh->segments_left == 0) 104140f04c3SDavid Lebrun return NULL; 105140f04c3SDavid Lebrun 106140f04c3SDavid Lebrun #ifdef CONFIG_IPV6_SEG6_HMAC 107140f04c3SDavid Lebrun if (!seg6_hmac_validate_skb(skb)) 108140f04c3SDavid Lebrun return NULL; 109140f04c3SDavid Lebrun #endif 110140f04c3SDavid Lebrun 111140f04c3SDavid Lebrun return srh; 112140f04c3SDavid Lebrun } 113140f04c3SDavid Lebrun 114d7a669ddSDavid Lebrun static bool decap_and_validate(struct sk_buff *skb, int proto) 115d7a669ddSDavid Lebrun { 116d7a669ddSDavid Lebrun struct ipv6_sr_hdr *srh; 117d7a669ddSDavid Lebrun unsigned int off = 0; 118d7a669ddSDavid Lebrun 119d7a669ddSDavid Lebrun srh = get_srh(skb); 120d7a669ddSDavid Lebrun if (srh && srh->segments_left > 0) 121d7a669ddSDavid Lebrun return false; 122d7a669ddSDavid Lebrun 123d7a669ddSDavid Lebrun #ifdef CONFIG_IPV6_SEG6_HMAC 124d7a669ddSDavid Lebrun if (srh && !seg6_hmac_validate_skb(skb)) 125d7a669ddSDavid Lebrun return false; 126d7a669ddSDavid Lebrun #endif 127d7a669ddSDavid Lebrun 128d7a669ddSDavid Lebrun if (ipv6_find_hdr(skb, &off, proto, NULL, NULL) < 0) 129d7a669ddSDavid Lebrun return false; 130d7a669ddSDavid Lebrun 131d7a669ddSDavid Lebrun if (!pskb_pull(skb, off)) 132d7a669ddSDavid Lebrun return false; 133d7a669ddSDavid Lebrun 134d7a669ddSDavid Lebrun skb_postpull_rcsum(skb, skb_network_header(skb), off); 135d7a669ddSDavid Lebrun 136d7a669ddSDavid Lebrun skb_reset_network_header(skb); 137d7a669ddSDavid Lebrun skb_reset_transport_header(skb); 138d7a669ddSDavid Lebrun skb->encapsulation = 0; 139d7a669ddSDavid Lebrun 140d7a669ddSDavid Lebrun return true; 141d7a669ddSDavid Lebrun } 142d7a669ddSDavid Lebrun 143d7a669ddSDavid Lebrun static void advance_nextseg(struct ipv6_sr_hdr *srh, struct in6_addr *daddr) 144d7a669ddSDavid Lebrun { 145d7a669ddSDavid Lebrun struct in6_addr *addr; 146d7a669ddSDavid Lebrun 147d7a669ddSDavid Lebrun srh->segments_left--; 148d7a669ddSDavid Lebrun addr = srh->segments + srh->segments_left; 149d7a669ddSDavid Lebrun *daddr = *addr; 150d7a669ddSDavid Lebrun } 151d7a669ddSDavid Lebrun 1521c1e761eSMathieu Xhonneux int seg6_lookup_nexthop(struct sk_buff *skb, struct in6_addr *nhaddr, 153d7a669ddSDavid Lebrun u32 tbl_id) 154d7a669ddSDavid Lebrun { 155d7a669ddSDavid Lebrun struct net *net = dev_net(skb->dev); 156d7a669ddSDavid Lebrun struct ipv6hdr *hdr = ipv6_hdr(skb); 157d7a669ddSDavid Lebrun int flags = RT6_LOOKUP_F_HAS_SADDR; 158d7a669ddSDavid Lebrun struct dst_entry *dst = NULL; 159d7a669ddSDavid Lebrun struct rt6_info *rt; 160d7a669ddSDavid Lebrun struct flowi6 fl6; 161d7a669ddSDavid Lebrun 162d7a669ddSDavid Lebrun fl6.flowi6_iif = skb->dev->ifindex; 163d7a669ddSDavid Lebrun fl6.daddr = nhaddr ? *nhaddr : hdr->daddr; 164d7a669ddSDavid Lebrun fl6.saddr = hdr->saddr; 165d7a669ddSDavid Lebrun fl6.flowlabel = ip6_flowinfo(hdr); 166d7a669ddSDavid Lebrun fl6.flowi6_mark = skb->mark; 167d7a669ddSDavid Lebrun fl6.flowi6_proto = hdr->nexthdr; 168d7a669ddSDavid Lebrun 169d7a669ddSDavid Lebrun if (nhaddr) 170d7a669ddSDavid Lebrun fl6.flowi6_flags = FLOWI_FLAG_KNOWN_NH; 171d7a669ddSDavid Lebrun 172d7a669ddSDavid Lebrun if (!tbl_id) { 173b75cc8f9SDavid Ahern dst = ip6_route_input_lookup(net, skb->dev, &fl6, skb, flags); 174d7a669ddSDavid Lebrun } else { 175d7a669ddSDavid Lebrun struct fib6_table *table; 176d7a669ddSDavid Lebrun 177d7a669ddSDavid Lebrun table = fib6_get_table(net, tbl_id); 178d7a669ddSDavid Lebrun if (!table) 179d7a669ddSDavid Lebrun goto out; 180d7a669ddSDavid Lebrun 181b75cc8f9SDavid Ahern rt = ip6_pol_route(net, table, 0, &fl6, skb, flags); 182d7a669ddSDavid Lebrun dst = &rt->dst; 183d7a669ddSDavid Lebrun } 184d7a669ddSDavid Lebrun 185d7a669ddSDavid Lebrun if (dst && dst->dev->flags & IFF_LOOPBACK && !dst->error) { 186d7a669ddSDavid Lebrun dst_release(dst); 187d7a669ddSDavid Lebrun dst = NULL; 188d7a669ddSDavid Lebrun } 189d7a669ddSDavid Lebrun 190d7a669ddSDavid Lebrun out: 191d7a669ddSDavid Lebrun if (!dst) { 192d7a669ddSDavid Lebrun rt = net->ipv6.ip6_blk_hole_entry; 193d7a669ddSDavid Lebrun dst = &rt->dst; 194d7a669ddSDavid Lebrun dst_hold(dst); 195d7a669ddSDavid Lebrun } 196d7a669ddSDavid Lebrun 197d7a669ddSDavid Lebrun skb_dst_drop(skb); 198d7a669ddSDavid Lebrun skb_dst_set(skb, dst); 1991c1e761eSMathieu Xhonneux return dst->error; 200d7a669ddSDavid Lebrun } 201d7a669ddSDavid Lebrun 202140f04c3SDavid Lebrun /* regular endpoint function */ 203140f04c3SDavid Lebrun static int input_action_end(struct sk_buff *skb, struct seg6_local_lwt *slwt) 204140f04c3SDavid Lebrun { 205140f04c3SDavid Lebrun struct ipv6_sr_hdr *srh; 206140f04c3SDavid Lebrun 207140f04c3SDavid Lebrun srh = get_and_validate_srh(skb); 208140f04c3SDavid Lebrun if (!srh) 209140f04c3SDavid Lebrun goto drop; 210140f04c3SDavid Lebrun 211d7a669ddSDavid Lebrun advance_nextseg(srh, &ipv6_hdr(skb)->daddr); 212140f04c3SDavid Lebrun 2131c1e761eSMathieu Xhonneux seg6_lookup_nexthop(skb, NULL, 0); 214140f04c3SDavid Lebrun 215140f04c3SDavid Lebrun return dst_input(skb); 216140f04c3SDavid Lebrun 217140f04c3SDavid Lebrun drop: 218140f04c3SDavid Lebrun kfree_skb(skb); 219140f04c3SDavid Lebrun return -EINVAL; 220140f04c3SDavid Lebrun } 221140f04c3SDavid Lebrun 222140f04c3SDavid Lebrun /* regular endpoint, and forward to specified nexthop */ 223140f04c3SDavid Lebrun static int input_action_end_x(struct sk_buff *skb, struct seg6_local_lwt *slwt) 224140f04c3SDavid Lebrun { 225140f04c3SDavid Lebrun struct ipv6_sr_hdr *srh; 226140f04c3SDavid Lebrun 227140f04c3SDavid Lebrun srh = get_and_validate_srh(skb); 228140f04c3SDavid Lebrun if (!srh) 229140f04c3SDavid Lebrun goto drop; 230140f04c3SDavid Lebrun 231d7a669ddSDavid Lebrun advance_nextseg(srh, &ipv6_hdr(skb)->daddr); 232140f04c3SDavid Lebrun 2331c1e761eSMathieu Xhonneux seg6_lookup_nexthop(skb, &slwt->nh6, 0); 234140f04c3SDavid Lebrun 235140f04c3SDavid Lebrun return dst_input(skb); 236140f04c3SDavid Lebrun 237140f04c3SDavid Lebrun drop: 238140f04c3SDavid Lebrun kfree_skb(skb); 239140f04c3SDavid Lebrun return -EINVAL; 240140f04c3SDavid Lebrun } 241140f04c3SDavid Lebrun 242891ef8ddSDavid Lebrun static int input_action_end_t(struct sk_buff *skb, struct seg6_local_lwt *slwt) 243891ef8ddSDavid Lebrun { 244891ef8ddSDavid Lebrun struct ipv6_sr_hdr *srh; 245891ef8ddSDavid Lebrun 246891ef8ddSDavid Lebrun srh = get_and_validate_srh(skb); 247891ef8ddSDavid Lebrun if (!srh) 248891ef8ddSDavid Lebrun goto drop; 249891ef8ddSDavid Lebrun 250891ef8ddSDavid Lebrun advance_nextseg(srh, &ipv6_hdr(skb)->daddr); 251891ef8ddSDavid Lebrun 2521c1e761eSMathieu Xhonneux seg6_lookup_nexthop(skb, NULL, slwt->table); 253891ef8ddSDavid Lebrun 254891ef8ddSDavid Lebrun return dst_input(skb); 255891ef8ddSDavid Lebrun 256891ef8ddSDavid Lebrun drop: 257891ef8ddSDavid Lebrun kfree_skb(skb); 258891ef8ddSDavid Lebrun return -EINVAL; 259891ef8ddSDavid Lebrun } 260891ef8ddSDavid Lebrun 261891ef8ddSDavid Lebrun /* decapsulate and forward inner L2 frame on specified interface */ 262891ef8ddSDavid Lebrun static int input_action_end_dx2(struct sk_buff *skb, 263891ef8ddSDavid Lebrun struct seg6_local_lwt *slwt) 264891ef8ddSDavid Lebrun { 265891ef8ddSDavid Lebrun struct net *net = dev_net(skb->dev); 266891ef8ddSDavid Lebrun struct net_device *odev; 267891ef8ddSDavid Lebrun struct ethhdr *eth; 268891ef8ddSDavid Lebrun 269891ef8ddSDavid Lebrun if (!decap_and_validate(skb, NEXTHDR_NONE)) 270891ef8ddSDavid Lebrun goto drop; 271891ef8ddSDavid Lebrun 272891ef8ddSDavid Lebrun if (!pskb_may_pull(skb, ETH_HLEN)) 273891ef8ddSDavid Lebrun goto drop; 274891ef8ddSDavid Lebrun 275891ef8ddSDavid Lebrun skb_reset_mac_header(skb); 276891ef8ddSDavid Lebrun eth = (struct ethhdr *)skb->data; 277891ef8ddSDavid Lebrun 278891ef8ddSDavid Lebrun /* To determine the frame's protocol, we assume it is 802.3. This avoids 279891ef8ddSDavid Lebrun * a call to eth_type_trans(), which is not really relevant for our 280891ef8ddSDavid Lebrun * use case. 281891ef8ddSDavid Lebrun */ 282891ef8ddSDavid Lebrun if (!eth_proto_is_802_3(eth->h_proto)) 283891ef8ddSDavid Lebrun goto drop; 284891ef8ddSDavid Lebrun 285891ef8ddSDavid Lebrun odev = dev_get_by_index_rcu(net, slwt->oif); 286891ef8ddSDavid Lebrun if (!odev) 287891ef8ddSDavid Lebrun goto drop; 288891ef8ddSDavid Lebrun 289891ef8ddSDavid Lebrun /* As we accept Ethernet frames, make sure the egress device is of 290891ef8ddSDavid Lebrun * the correct type. 291891ef8ddSDavid Lebrun */ 292891ef8ddSDavid Lebrun if (odev->type != ARPHRD_ETHER) 293891ef8ddSDavid Lebrun goto drop; 294891ef8ddSDavid Lebrun 295891ef8ddSDavid Lebrun if (!(odev->flags & IFF_UP) || !netif_carrier_ok(odev)) 296891ef8ddSDavid Lebrun goto drop; 297891ef8ddSDavid Lebrun 298891ef8ddSDavid Lebrun skb_orphan(skb); 299891ef8ddSDavid Lebrun 300891ef8ddSDavid Lebrun if (skb_warn_if_lro(skb)) 301891ef8ddSDavid Lebrun goto drop; 302891ef8ddSDavid Lebrun 303891ef8ddSDavid Lebrun skb_forward_csum(skb); 304891ef8ddSDavid Lebrun 305891ef8ddSDavid Lebrun if (skb->len - ETH_HLEN > odev->mtu) 306891ef8ddSDavid Lebrun goto drop; 307891ef8ddSDavid Lebrun 308891ef8ddSDavid Lebrun skb->dev = odev; 309891ef8ddSDavid Lebrun skb->protocol = eth->h_proto; 310891ef8ddSDavid Lebrun 311891ef8ddSDavid Lebrun return dev_queue_xmit(skb); 312891ef8ddSDavid Lebrun 313891ef8ddSDavid Lebrun drop: 314891ef8ddSDavid Lebrun kfree_skb(skb); 315891ef8ddSDavid Lebrun return -EINVAL; 316891ef8ddSDavid Lebrun } 317891ef8ddSDavid Lebrun 318140f04c3SDavid Lebrun /* decapsulate and forward to specified nexthop */ 319140f04c3SDavid Lebrun static int input_action_end_dx6(struct sk_buff *skb, 320140f04c3SDavid Lebrun struct seg6_local_lwt *slwt) 321140f04c3SDavid Lebrun { 322d7a669ddSDavid Lebrun struct in6_addr *nhaddr = NULL; 323140f04c3SDavid Lebrun 324140f04c3SDavid Lebrun /* this function accepts IPv6 encapsulated packets, with either 325140f04c3SDavid Lebrun * an SRH with SL=0, or no SRH. 326140f04c3SDavid Lebrun */ 327140f04c3SDavid Lebrun 328d7a669ddSDavid Lebrun if (!decap_and_validate(skb, IPPROTO_IPV6)) 329140f04c3SDavid Lebrun goto drop; 330140f04c3SDavid Lebrun 331d7a669ddSDavid Lebrun if (!pskb_may_pull(skb, sizeof(struct ipv6hdr))) 332140f04c3SDavid Lebrun goto drop; 333140f04c3SDavid Lebrun 334140f04c3SDavid Lebrun /* The inner packet is not associated to any local interface, 335140f04c3SDavid Lebrun * so we do not call netif_rx(). 336140f04c3SDavid Lebrun * 337140f04c3SDavid Lebrun * If slwt->nh6 is set to ::, then lookup the nexthop for the 338140f04c3SDavid Lebrun * inner packet's DA. Otherwise, use the specified nexthop. 339140f04c3SDavid Lebrun */ 340140f04c3SDavid Lebrun 341d7a669ddSDavid Lebrun if (!ipv6_addr_any(&slwt->nh6)) 342d7a669ddSDavid Lebrun nhaddr = &slwt->nh6; 343140f04c3SDavid Lebrun 3441c1e761eSMathieu Xhonneux seg6_lookup_nexthop(skb, nhaddr, 0); 345140f04c3SDavid Lebrun 346140f04c3SDavid Lebrun return dst_input(skb); 347140f04c3SDavid Lebrun drop: 348140f04c3SDavid Lebrun kfree_skb(skb); 349140f04c3SDavid Lebrun return -EINVAL; 350140f04c3SDavid Lebrun } 351140f04c3SDavid Lebrun 352891ef8ddSDavid Lebrun static int input_action_end_dx4(struct sk_buff *skb, 353891ef8ddSDavid Lebrun struct seg6_local_lwt *slwt) 354891ef8ddSDavid Lebrun { 355891ef8ddSDavid Lebrun struct iphdr *iph; 356891ef8ddSDavid Lebrun __be32 nhaddr; 357891ef8ddSDavid Lebrun int err; 358891ef8ddSDavid Lebrun 359891ef8ddSDavid Lebrun if (!decap_and_validate(skb, IPPROTO_IPIP)) 360891ef8ddSDavid Lebrun goto drop; 361891ef8ddSDavid Lebrun 362891ef8ddSDavid Lebrun if (!pskb_may_pull(skb, sizeof(struct iphdr))) 363891ef8ddSDavid Lebrun goto drop; 364891ef8ddSDavid Lebrun 365891ef8ddSDavid Lebrun skb->protocol = htons(ETH_P_IP); 366891ef8ddSDavid Lebrun 367891ef8ddSDavid Lebrun iph = ip_hdr(skb); 368891ef8ddSDavid Lebrun 369891ef8ddSDavid Lebrun nhaddr = slwt->nh4.s_addr ?: iph->daddr; 370891ef8ddSDavid Lebrun 371891ef8ddSDavid Lebrun skb_dst_drop(skb); 372891ef8ddSDavid Lebrun 373891ef8ddSDavid Lebrun err = ip_route_input(skb, nhaddr, iph->saddr, 0, skb->dev); 374891ef8ddSDavid Lebrun if (err) 375891ef8ddSDavid Lebrun goto drop; 376891ef8ddSDavid Lebrun 377891ef8ddSDavid Lebrun return dst_input(skb); 378891ef8ddSDavid Lebrun 379891ef8ddSDavid Lebrun drop: 380891ef8ddSDavid Lebrun kfree_skb(skb); 381891ef8ddSDavid Lebrun return -EINVAL; 382891ef8ddSDavid Lebrun } 383891ef8ddSDavid Lebrun 384891ef8ddSDavid Lebrun static int input_action_end_dt6(struct sk_buff *skb, 385891ef8ddSDavid Lebrun struct seg6_local_lwt *slwt) 386891ef8ddSDavid Lebrun { 387891ef8ddSDavid Lebrun if (!decap_and_validate(skb, IPPROTO_IPV6)) 388891ef8ddSDavid Lebrun goto drop; 389891ef8ddSDavid Lebrun 390891ef8ddSDavid Lebrun if (!pskb_may_pull(skb, sizeof(struct ipv6hdr))) 391891ef8ddSDavid Lebrun goto drop; 392891ef8ddSDavid Lebrun 3931c1e761eSMathieu Xhonneux seg6_lookup_nexthop(skb, NULL, slwt->table); 394891ef8ddSDavid Lebrun 395891ef8ddSDavid Lebrun return dst_input(skb); 396891ef8ddSDavid Lebrun 397891ef8ddSDavid Lebrun drop: 398891ef8ddSDavid Lebrun kfree_skb(skb); 399891ef8ddSDavid Lebrun return -EINVAL; 400891ef8ddSDavid Lebrun } 401891ef8ddSDavid Lebrun 402140f04c3SDavid Lebrun /* push an SRH on top of the current one */ 403140f04c3SDavid Lebrun static int input_action_end_b6(struct sk_buff *skb, struct seg6_local_lwt *slwt) 404140f04c3SDavid Lebrun { 405140f04c3SDavid Lebrun struct ipv6_sr_hdr *srh; 406140f04c3SDavid Lebrun int err = -EINVAL; 407140f04c3SDavid Lebrun 408140f04c3SDavid Lebrun srh = get_and_validate_srh(skb); 409140f04c3SDavid Lebrun if (!srh) 410140f04c3SDavid Lebrun goto drop; 411140f04c3SDavid Lebrun 412140f04c3SDavid Lebrun err = seg6_do_srh_inline(skb, slwt->srh); 413140f04c3SDavid Lebrun if (err) 414140f04c3SDavid Lebrun goto drop; 415140f04c3SDavid Lebrun 416140f04c3SDavid Lebrun ipv6_hdr(skb)->payload_len = htons(skb->len - sizeof(struct ipv6hdr)); 417140f04c3SDavid Lebrun skb_set_transport_header(skb, sizeof(struct ipv6hdr)); 418140f04c3SDavid Lebrun 4191c1e761eSMathieu Xhonneux seg6_lookup_nexthop(skb, NULL, 0); 420140f04c3SDavid Lebrun 421140f04c3SDavid Lebrun return dst_input(skb); 422140f04c3SDavid Lebrun 423140f04c3SDavid Lebrun drop: 424140f04c3SDavid Lebrun kfree_skb(skb); 425140f04c3SDavid Lebrun return err; 426140f04c3SDavid Lebrun } 427140f04c3SDavid Lebrun 428140f04c3SDavid Lebrun /* encapsulate within an outer IPv6 header and a specified SRH */ 429140f04c3SDavid Lebrun static int input_action_end_b6_encap(struct sk_buff *skb, 430140f04c3SDavid Lebrun struct seg6_local_lwt *slwt) 431140f04c3SDavid Lebrun { 432140f04c3SDavid Lebrun struct ipv6_sr_hdr *srh; 433140f04c3SDavid Lebrun int err = -EINVAL; 434140f04c3SDavid Lebrun 435140f04c3SDavid Lebrun srh = get_and_validate_srh(skb); 436140f04c3SDavid Lebrun if (!srh) 437140f04c3SDavid Lebrun goto drop; 438140f04c3SDavid Lebrun 439d7a669ddSDavid Lebrun advance_nextseg(srh, &ipv6_hdr(skb)->daddr); 440140f04c3SDavid Lebrun 441140f04c3SDavid Lebrun skb_reset_inner_headers(skb); 442140f04c3SDavid Lebrun skb->encapsulation = 1; 443140f04c3SDavid Lebrun 44432d99d0bSDavid Lebrun err = seg6_do_srh_encap(skb, slwt->srh, IPPROTO_IPV6); 445140f04c3SDavid Lebrun if (err) 446140f04c3SDavid Lebrun goto drop; 447140f04c3SDavid Lebrun 448140f04c3SDavid Lebrun ipv6_hdr(skb)->payload_len = htons(skb->len - sizeof(struct ipv6hdr)); 449140f04c3SDavid Lebrun skb_set_transport_header(skb, sizeof(struct ipv6hdr)); 450140f04c3SDavid Lebrun 4511c1e761eSMathieu Xhonneux seg6_lookup_nexthop(skb, NULL, 0); 452140f04c3SDavid Lebrun 453140f04c3SDavid Lebrun return dst_input(skb); 454140f04c3SDavid Lebrun 455140f04c3SDavid Lebrun drop: 456140f04c3SDavid Lebrun kfree_skb(skb); 457140f04c3SDavid Lebrun return err; 458140f04c3SDavid Lebrun } 459140f04c3SDavid Lebrun 460fe94cc29SMathieu Xhonneux DEFINE_PER_CPU(struct seg6_bpf_srh_state, seg6_bpf_srh_states); 461fe94cc29SMathieu Xhonneux 462004d4b27SMathieu Xhonneux static int input_action_end_bpf(struct sk_buff *skb, 463004d4b27SMathieu Xhonneux struct seg6_local_lwt *slwt) 464004d4b27SMathieu Xhonneux { 465004d4b27SMathieu Xhonneux struct seg6_bpf_srh_state *srh_state = 466004d4b27SMathieu Xhonneux this_cpu_ptr(&seg6_bpf_srh_states); 467004d4b27SMathieu Xhonneux struct seg6_bpf_srh_state local_srh_state; 468004d4b27SMathieu Xhonneux struct ipv6_sr_hdr *srh; 469004d4b27SMathieu Xhonneux int srhoff = 0; 470004d4b27SMathieu Xhonneux int ret; 471004d4b27SMathieu Xhonneux 472004d4b27SMathieu Xhonneux srh = get_and_validate_srh(skb); 473004d4b27SMathieu Xhonneux if (!srh) 474004d4b27SMathieu Xhonneux goto drop; 475004d4b27SMathieu Xhonneux advance_nextseg(srh, &ipv6_hdr(skb)->daddr); 476004d4b27SMathieu Xhonneux 477004d4b27SMathieu Xhonneux /* preempt_disable is needed to protect the per-CPU buffer srh_state, 478004d4b27SMathieu Xhonneux * which is also accessed by the bpf_lwt_seg6_* helpers 479004d4b27SMathieu Xhonneux */ 480004d4b27SMathieu Xhonneux preempt_disable(); 481004d4b27SMathieu Xhonneux srh_state->hdrlen = srh->hdrlen << 3; 482004d4b27SMathieu Xhonneux srh_state->valid = 1; 483004d4b27SMathieu Xhonneux 484004d4b27SMathieu Xhonneux rcu_read_lock(); 485004d4b27SMathieu Xhonneux bpf_compute_data_pointers(skb); 486004d4b27SMathieu Xhonneux ret = bpf_prog_run_save_cb(slwt->bpf.prog, skb); 487004d4b27SMathieu Xhonneux rcu_read_unlock(); 488004d4b27SMathieu Xhonneux 489004d4b27SMathieu Xhonneux local_srh_state = *srh_state; 490004d4b27SMathieu Xhonneux preempt_enable(); 491004d4b27SMathieu Xhonneux 492004d4b27SMathieu Xhonneux switch (ret) { 493004d4b27SMathieu Xhonneux case BPF_OK: 494004d4b27SMathieu Xhonneux case BPF_REDIRECT: 495004d4b27SMathieu Xhonneux break; 496004d4b27SMathieu Xhonneux case BPF_DROP: 497004d4b27SMathieu Xhonneux goto drop; 498004d4b27SMathieu Xhonneux default: 499004d4b27SMathieu Xhonneux pr_warn_once("bpf-seg6local: Illegal return value %u\n", ret); 500004d4b27SMathieu Xhonneux goto drop; 501004d4b27SMathieu Xhonneux } 502004d4b27SMathieu Xhonneux 503004d4b27SMathieu Xhonneux if (unlikely((local_srh_state.hdrlen & 7) != 0)) 504004d4b27SMathieu Xhonneux goto drop; 505004d4b27SMathieu Xhonneux 506004d4b27SMathieu Xhonneux if (ipv6_find_hdr(skb, &srhoff, IPPROTO_ROUTING, NULL, NULL) < 0) 507004d4b27SMathieu Xhonneux goto drop; 508004d4b27SMathieu Xhonneux srh = (struct ipv6_sr_hdr *)(skb->data + srhoff); 509004d4b27SMathieu Xhonneux srh->hdrlen = (u8)(local_srh_state.hdrlen >> 3); 510004d4b27SMathieu Xhonneux 511004d4b27SMathieu Xhonneux if (!local_srh_state.valid && 512004d4b27SMathieu Xhonneux unlikely(!seg6_validate_srh(srh, (srh->hdrlen + 1) << 3))) 513004d4b27SMathieu Xhonneux goto drop; 514004d4b27SMathieu Xhonneux 515004d4b27SMathieu Xhonneux if (ret != BPF_REDIRECT) 516004d4b27SMathieu Xhonneux seg6_lookup_nexthop(skb, NULL, 0); 517004d4b27SMathieu Xhonneux 518004d4b27SMathieu Xhonneux return dst_input(skb); 519004d4b27SMathieu Xhonneux 520004d4b27SMathieu Xhonneux drop: 521004d4b27SMathieu Xhonneux kfree_skb(skb); 522004d4b27SMathieu Xhonneux return -EINVAL; 523004d4b27SMathieu Xhonneux } 524004d4b27SMathieu Xhonneux 525d1df6fd8SDavid Lebrun static struct seg6_action_desc seg6_action_table[] = { 526d1df6fd8SDavid Lebrun { 527d1df6fd8SDavid Lebrun .action = SEG6_LOCAL_ACTION_END, 528d1df6fd8SDavid Lebrun .attrs = 0, 529140f04c3SDavid Lebrun .input = input_action_end, 530d1df6fd8SDavid Lebrun }, 531140f04c3SDavid Lebrun { 532140f04c3SDavid Lebrun .action = SEG6_LOCAL_ACTION_END_X, 533140f04c3SDavid Lebrun .attrs = (1 << SEG6_LOCAL_NH6), 534140f04c3SDavid Lebrun .input = input_action_end_x, 535140f04c3SDavid Lebrun }, 536140f04c3SDavid Lebrun { 537891ef8ddSDavid Lebrun .action = SEG6_LOCAL_ACTION_END_T, 538891ef8ddSDavid Lebrun .attrs = (1 << SEG6_LOCAL_TABLE), 539891ef8ddSDavid Lebrun .input = input_action_end_t, 540891ef8ddSDavid Lebrun }, 541891ef8ddSDavid Lebrun { 542891ef8ddSDavid Lebrun .action = SEG6_LOCAL_ACTION_END_DX2, 543891ef8ddSDavid Lebrun .attrs = (1 << SEG6_LOCAL_OIF), 544891ef8ddSDavid Lebrun .input = input_action_end_dx2, 545891ef8ddSDavid Lebrun }, 546891ef8ddSDavid Lebrun { 547140f04c3SDavid Lebrun .action = SEG6_LOCAL_ACTION_END_DX6, 548140f04c3SDavid Lebrun .attrs = (1 << SEG6_LOCAL_NH6), 549140f04c3SDavid Lebrun .input = input_action_end_dx6, 550140f04c3SDavid Lebrun }, 551140f04c3SDavid Lebrun { 552891ef8ddSDavid Lebrun .action = SEG6_LOCAL_ACTION_END_DX4, 553891ef8ddSDavid Lebrun .attrs = (1 << SEG6_LOCAL_NH4), 554891ef8ddSDavid Lebrun .input = input_action_end_dx4, 555891ef8ddSDavid Lebrun }, 556891ef8ddSDavid Lebrun { 557891ef8ddSDavid Lebrun .action = SEG6_LOCAL_ACTION_END_DT6, 558891ef8ddSDavid Lebrun .attrs = (1 << SEG6_LOCAL_TABLE), 559891ef8ddSDavid Lebrun .input = input_action_end_dt6, 560891ef8ddSDavid Lebrun }, 561891ef8ddSDavid Lebrun { 562140f04c3SDavid Lebrun .action = SEG6_LOCAL_ACTION_END_B6, 563140f04c3SDavid Lebrun .attrs = (1 << SEG6_LOCAL_SRH), 564140f04c3SDavid Lebrun .input = input_action_end_b6, 565140f04c3SDavid Lebrun }, 566140f04c3SDavid Lebrun { 567140f04c3SDavid Lebrun .action = SEG6_LOCAL_ACTION_END_B6_ENCAP, 568140f04c3SDavid Lebrun .attrs = (1 << SEG6_LOCAL_SRH), 569140f04c3SDavid Lebrun .input = input_action_end_b6_encap, 570140f04c3SDavid Lebrun .static_headroom = sizeof(struct ipv6hdr), 571004d4b27SMathieu Xhonneux }, 572004d4b27SMathieu Xhonneux { 573004d4b27SMathieu Xhonneux .action = SEG6_LOCAL_ACTION_END_BPF, 574004d4b27SMathieu Xhonneux .attrs = (1 << SEG6_LOCAL_BPF), 575004d4b27SMathieu Xhonneux .input = input_action_end_bpf, 576004d4b27SMathieu Xhonneux }, 577004d4b27SMathieu Xhonneux 578d1df6fd8SDavid Lebrun }; 579d1df6fd8SDavid Lebrun 580d1df6fd8SDavid Lebrun static struct seg6_action_desc *__get_action_desc(int action) 581d1df6fd8SDavid Lebrun { 582d1df6fd8SDavid Lebrun struct seg6_action_desc *desc; 583d1df6fd8SDavid Lebrun int i, count; 584d1df6fd8SDavid Lebrun 585709af180SColin Ian King count = ARRAY_SIZE(seg6_action_table); 586d1df6fd8SDavid Lebrun for (i = 0; i < count; i++) { 587d1df6fd8SDavid Lebrun desc = &seg6_action_table[i]; 588d1df6fd8SDavid Lebrun if (desc->action == action) 589d1df6fd8SDavid Lebrun return desc; 590d1df6fd8SDavid Lebrun } 591d1df6fd8SDavid Lebrun 592d1df6fd8SDavid Lebrun return NULL; 593d1df6fd8SDavid Lebrun } 594d1df6fd8SDavid Lebrun 595d1df6fd8SDavid Lebrun static int seg6_local_input(struct sk_buff *skb) 596d1df6fd8SDavid Lebrun { 597d1df6fd8SDavid Lebrun struct dst_entry *orig_dst = skb_dst(skb); 598d1df6fd8SDavid Lebrun struct seg6_action_desc *desc; 599d1df6fd8SDavid Lebrun struct seg6_local_lwt *slwt; 600d1df6fd8SDavid Lebrun 6016285217fSDavid Lebrun if (skb->protocol != htons(ETH_P_IPV6)) { 6026285217fSDavid Lebrun kfree_skb(skb); 6036285217fSDavid Lebrun return -EINVAL; 6046285217fSDavid Lebrun } 6056285217fSDavid Lebrun 606d1df6fd8SDavid Lebrun slwt = seg6_local_lwtunnel(orig_dst->lwtstate); 607d1df6fd8SDavid Lebrun desc = slwt->desc; 608d1df6fd8SDavid Lebrun 609d1df6fd8SDavid Lebrun return desc->input(skb, slwt); 610d1df6fd8SDavid Lebrun } 611d1df6fd8SDavid Lebrun 612d1df6fd8SDavid Lebrun static const struct nla_policy seg6_local_policy[SEG6_LOCAL_MAX + 1] = { 613d1df6fd8SDavid Lebrun [SEG6_LOCAL_ACTION] = { .type = NLA_U32 }, 614d1df6fd8SDavid Lebrun [SEG6_LOCAL_SRH] = { .type = NLA_BINARY }, 615d1df6fd8SDavid Lebrun [SEG6_LOCAL_TABLE] = { .type = NLA_U32 }, 616d1df6fd8SDavid Lebrun [SEG6_LOCAL_NH4] = { .type = NLA_BINARY, 617d1df6fd8SDavid Lebrun .len = sizeof(struct in_addr) }, 618d1df6fd8SDavid Lebrun [SEG6_LOCAL_NH6] = { .type = NLA_BINARY, 619d1df6fd8SDavid Lebrun .len = sizeof(struct in6_addr) }, 620d1df6fd8SDavid Lebrun [SEG6_LOCAL_IIF] = { .type = NLA_U32 }, 621d1df6fd8SDavid Lebrun [SEG6_LOCAL_OIF] = { .type = NLA_U32 }, 622004d4b27SMathieu Xhonneux [SEG6_LOCAL_BPF] = { .type = NLA_NESTED }, 623d1df6fd8SDavid Lebrun }; 624d1df6fd8SDavid Lebrun 6252d9cc60aSDavid Lebrun static int parse_nla_srh(struct nlattr **attrs, struct seg6_local_lwt *slwt) 6262d9cc60aSDavid Lebrun { 6272d9cc60aSDavid Lebrun struct ipv6_sr_hdr *srh; 6282d9cc60aSDavid Lebrun int len; 6292d9cc60aSDavid Lebrun 6302d9cc60aSDavid Lebrun srh = nla_data(attrs[SEG6_LOCAL_SRH]); 6312d9cc60aSDavid Lebrun len = nla_len(attrs[SEG6_LOCAL_SRH]); 6322d9cc60aSDavid Lebrun 6332d9cc60aSDavid Lebrun /* SRH must contain at least one segment */ 6342d9cc60aSDavid Lebrun if (len < sizeof(*srh) + sizeof(struct in6_addr)) 6352d9cc60aSDavid Lebrun return -EINVAL; 6362d9cc60aSDavid Lebrun 6372d9cc60aSDavid Lebrun if (!seg6_validate_srh(srh, len)) 6382d9cc60aSDavid Lebrun return -EINVAL; 6392d9cc60aSDavid Lebrun 640*7fa41efaSYueHaibing slwt->srh = kmemdup(srh, len, GFP_KERNEL); 6412d9cc60aSDavid Lebrun if (!slwt->srh) 6422d9cc60aSDavid Lebrun return -ENOMEM; 6432d9cc60aSDavid Lebrun 6442d9cc60aSDavid Lebrun slwt->headroom += len; 6452d9cc60aSDavid Lebrun 6462d9cc60aSDavid Lebrun return 0; 6472d9cc60aSDavid Lebrun } 6482d9cc60aSDavid Lebrun 6492d9cc60aSDavid Lebrun static int put_nla_srh(struct sk_buff *skb, struct seg6_local_lwt *slwt) 6502d9cc60aSDavid Lebrun { 6512d9cc60aSDavid Lebrun struct ipv6_sr_hdr *srh; 6522d9cc60aSDavid Lebrun struct nlattr *nla; 6532d9cc60aSDavid Lebrun int len; 6542d9cc60aSDavid Lebrun 6552d9cc60aSDavid Lebrun srh = slwt->srh; 6562d9cc60aSDavid Lebrun len = (srh->hdrlen + 1) << 3; 6572d9cc60aSDavid Lebrun 6582d9cc60aSDavid Lebrun nla = nla_reserve(skb, SEG6_LOCAL_SRH, len); 6592d9cc60aSDavid Lebrun if (!nla) 6602d9cc60aSDavid Lebrun return -EMSGSIZE; 6612d9cc60aSDavid Lebrun 6622d9cc60aSDavid Lebrun memcpy(nla_data(nla), srh, len); 6632d9cc60aSDavid Lebrun 6642d9cc60aSDavid Lebrun return 0; 6652d9cc60aSDavid Lebrun } 6662d9cc60aSDavid Lebrun 6672d9cc60aSDavid Lebrun static int cmp_nla_srh(struct seg6_local_lwt *a, struct seg6_local_lwt *b) 6682d9cc60aSDavid Lebrun { 6692d9cc60aSDavid Lebrun int len = (a->srh->hdrlen + 1) << 3; 6702d9cc60aSDavid Lebrun 6712d9cc60aSDavid Lebrun if (len != ((b->srh->hdrlen + 1) << 3)) 6722d9cc60aSDavid Lebrun return 1; 6732d9cc60aSDavid Lebrun 6742d9cc60aSDavid Lebrun return memcmp(a->srh, b->srh, len); 6752d9cc60aSDavid Lebrun } 6762d9cc60aSDavid Lebrun 6772d9cc60aSDavid Lebrun static int parse_nla_table(struct nlattr **attrs, struct seg6_local_lwt *slwt) 6782d9cc60aSDavid Lebrun { 6792d9cc60aSDavid Lebrun slwt->table = nla_get_u32(attrs[SEG6_LOCAL_TABLE]); 6802d9cc60aSDavid Lebrun 6812d9cc60aSDavid Lebrun return 0; 6822d9cc60aSDavid Lebrun } 6832d9cc60aSDavid Lebrun 6842d9cc60aSDavid Lebrun static int put_nla_table(struct sk_buff *skb, struct seg6_local_lwt *slwt) 6852d9cc60aSDavid Lebrun { 6862d9cc60aSDavid Lebrun if (nla_put_u32(skb, SEG6_LOCAL_TABLE, slwt->table)) 6872d9cc60aSDavid Lebrun return -EMSGSIZE; 6882d9cc60aSDavid Lebrun 6892d9cc60aSDavid Lebrun return 0; 6902d9cc60aSDavid Lebrun } 6912d9cc60aSDavid Lebrun 6922d9cc60aSDavid Lebrun static int cmp_nla_table(struct seg6_local_lwt *a, struct seg6_local_lwt *b) 6932d9cc60aSDavid Lebrun { 6942d9cc60aSDavid Lebrun if (a->table != b->table) 6952d9cc60aSDavid Lebrun return 1; 6962d9cc60aSDavid Lebrun 6972d9cc60aSDavid Lebrun return 0; 6982d9cc60aSDavid Lebrun } 6992d9cc60aSDavid Lebrun 7002d9cc60aSDavid Lebrun static int parse_nla_nh4(struct nlattr **attrs, struct seg6_local_lwt *slwt) 7012d9cc60aSDavid Lebrun { 7022d9cc60aSDavid Lebrun memcpy(&slwt->nh4, nla_data(attrs[SEG6_LOCAL_NH4]), 7032d9cc60aSDavid Lebrun sizeof(struct in_addr)); 7042d9cc60aSDavid Lebrun 7052d9cc60aSDavid Lebrun return 0; 7062d9cc60aSDavid Lebrun } 7072d9cc60aSDavid Lebrun 7082d9cc60aSDavid Lebrun static int put_nla_nh4(struct sk_buff *skb, struct seg6_local_lwt *slwt) 7092d9cc60aSDavid Lebrun { 7102d9cc60aSDavid Lebrun struct nlattr *nla; 7112d9cc60aSDavid Lebrun 7122d9cc60aSDavid Lebrun nla = nla_reserve(skb, SEG6_LOCAL_NH4, sizeof(struct in_addr)); 7132d9cc60aSDavid Lebrun if (!nla) 7142d9cc60aSDavid Lebrun return -EMSGSIZE; 7152d9cc60aSDavid Lebrun 7162d9cc60aSDavid Lebrun memcpy(nla_data(nla), &slwt->nh4, sizeof(struct in_addr)); 7172d9cc60aSDavid Lebrun 7182d9cc60aSDavid Lebrun return 0; 7192d9cc60aSDavid Lebrun } 7202d9cc60aSDavid Lebrun 7212d9cc60aSDavid Lebrun static int cmp_nla_nh4(struct seg6_local_lwt *a, struct seg6_local_lwt *b) 7222d9cc60aSDavid Lebrun { 7232d9cc60aSDavid Lebrun return memcmp(&a->nh4, &b->nh4, sizeof(struct in_addr)); 7242d9cc60aSDavid Lebrun } 7252d9cc60aSDavid Lebrun 7262d9cc60aSDavid Lebrun static int parse_nla_nh6(struct nlattr **attrs, struct seg6_local_lwt *slwt) 7272d9cc60aSDavid Lebrun { 7282d9cc60aSDavid Lebrun memcpy(&slwt->nh6, nla_data(attrs[SEG6_LOCAL_NH6]), 7292d9cc60aSDavid Lebrun sizeof(struct in6_addr)); 7302d9cc60aSDavid Lebrun 7312d9cc60aSDavid Lebrun return 0; 7322d9cc60aSDavid Lebrun } 7332d9cc60aSDavid Lebrun 7342d9cc60aSDavid Lebrun static int put_nla_nh6(struct sk_buff *skb, struct seg6_local_lwt *slwt) 7352d9cc60aSDavid Lebrun { 7362d9cc60aSDavid Lebrun struct nlattr *nla; 7372d9cc60aSDavid Lebrun 7382d9cc60aSDavid Lebrun nla = nla_reserve(skb, SEG6_LOCAL_NH6, sizeof(struct in6_addr)); 7392d9cc60aSDavid Lebrun if (!nla) 7402d9cc60aSDavid Lebrun return -EMSGSIZE; 7412d9cc60aSDavid Lebrun 7422d9cc60aSDavid Lebrun memcpy(nla_data(nla), &slwt->nh6, sizeof(struct in6_addr)); 7432d9cc60aSDavid Lebrun 7442d9cc60aSDavid Lebrun return 0; 7452d9cc60aSDavid Lebrun } 7462d9cc60aSDavid Lebrun 7472d9cc60aSDavid Lebrun static int cmp_nla_nh6(struct seg6_local_lwt *a, struct seg6_local_lwt *b) 7482d9cc60aSDavid Lebrun { 7492d9cc60aSDavid Lebrun return memcmp(&a->nh6, &b->nh6, sizeof(struct in6_addr)); 7502d9cc60aSDavid Lebrun } 7512d9cc60aSDavid Lebrun 7522d9cc60aSDavid Lebrun static int parse_nla_iif(struct nlattr **attrs, struct seg6_local_lwt *slwt) 7532d9cc60aSDavid Lebrun { 7542d9cc60aSDavid Lebrun slwt->iif = nla_get_u32(attrs[SEG6_LOCAL_IIF]); 7552d9cc60aSDavid Lebrun 7562d9cc60aSDavid Lebrun return 0; 7572d9cc60aSDavid Lebrun } 7582d9cc60aSDavid Lebrun 7592d9cc60aSDavid Lebrun static int put_nla_iif(struct sk_buff *skb, struct seg6_local_lwt *slwt) 7602d9cc60aSDavid Lebrun { 7612d9cc60aSDavid Lebrun if (nla_put_u32(skb, SEG6_LOCAL_IIF, slwt->iif)) 7622d9cc60aSDavid Lebrun return -EMSGSIZE; 7632d9cc60aSDavid Lebrun 7642d9cc60aSDavid Lebrun return 0; 7652d9cc60aSDavid Lebrun } 7662d9cc60aSDavid Lebrun 7672d9cc60aSDavid Lebrun static int cmp_nla_iif(struct seg6_local_lwt *a, struct seg6_local_lwt *b) 7682d9cc60aSDavid Lebrun { 7692d9cc60aSDavid Lebrun if (a->iif != b->iif) 7702d9cc60aSDavid Lebrun return 1; 7712d9cc60aSDavid Lebrun 7722d9cc60aSDavid Lebrun return 0; 7732d9cc60aSDavid Lebrun } 7742d9cc60aSDavid Lebrun 7752d9cc60aSDavid Lebrun static int parse_nla_oif(struct nlattr **attrs, struct seg6_local_lwt *slwt) 7762d9cc60aSDavid Lebrun { 7772d9cc60aSDavid Lebrun slwt->oif = nla_get_u32(attrs[SEG6_LOCAL_OIF]); 7782d9cc60aSDavid Lebrun 7792d9cc60aSDavid Lebrun return 0; 7802d9cc60aSDavid Lebrun } 7812d9cc60aSDavid Lebrun 7822d9cc60aSDavid Lebrun static int put_nla_oif(struct sk_buff *skb, struct seg6_local_lwt *slwt) 7832d9cc60aSDavid Lebrun { 7842d9cc60aSDavid Lebrun if (nla_put_u32(skb, SEG6_LOCAL_OIF, slwt->oif)) 7852d9cc60aSDavid Lebrun return -EMSGSIZE; 7862d9cc60aSDavid Lebrun 7872d9cc60aSDavid Lebrun return 0; 7882d9cc60aSDavid Lebrun } 7892d9cc60aSDavid Lebrun 7902d9cc60aSDavid Lebrun static int cmp_nla_oif(struct seg6_local_lwt *a, struct seg6_local_lwt *b) 7912d9cc60aSDavid Lebrun { 7922d9cc60aSDavid Lebrun if (a->oif != b->oif) 7932d9cc60aSDavid Lebrun return 1; 7942d9cc60aSDavid Lebrun 7952d9cc60aSDavid Lebrun return 0; 7962d9cc60aSDavid Lebrun } 7972d9cc60aSDavid Lebrun 798004d4b27SMathieu Xhonneux #define MAX_PROG_NAME 256 799004d4b27SMathieu Xhonneux static const struct nla_policy bpf_prog_policy[SEG6_LOCAL_BPF_PROG_MAX + 1] = { 800004d4b27SMathieu Xhonneux [SEG6_LOCAL_BPF_PROG] = { .type = NLA_U32, }, 801004d4b27SMathieu Xhonneux [SEG6_LOCAL_BPF_PROG_NAME] = { .type = NLA_NUL_STRING, 802004d4b27SMathieu Xhonneux .len = MAX_PROG_NAME }, 803004d4b27SMathieu Xhonneux }; 804004d4b27SMathieu Xhonneux 805004d4b27SMathieu Xhonneux static int parse_nla_bpf(struct nlattr **attrs, struct seg6_local_lwt *slwt) 806004d4b27SMathieu Xhonneux { 807004d4b27SMathieu Xhonneux struct nlattr *tb[SEG6_LOCAL_BPF_PROG_MAX + 1]; 808004d4b27SMathieu Xhonneux struct bpf_prog *p; 809004d4b27SMathieu Xhonneux int ret; 810004d4b27SMathieu Xhonneux u32 fd; 811004d4b27SMathieu Xhonneux 812004d4b27SMathieu Xhonneux ret = nla_parse_nested(tb, SEG6_LOCAL_BPF_PROG_MAX, 813004d4b27SMathieu Xhonneux attrs[SEG6_LOCAL_BPF], bpf_prog_policy, NULL); 814004d4b27SMathieu Xhonneux if (ret < 0) 815004d4b27SMathieu Xhonneux return ret; 816004d4b27SMathieu Xhonneux 817004d4b27SMathieu Xhonneux if (!tb[SEG6_LOCAL_BPF_PROG] || !tb[SEG6_LOCAL_BPF_PROG_NAME]) 818004d4b27SMathieu Xhonneux return -EINVAL; 819004d4b27SMathieu Xhonneux 820004d4b27SMathieu Xhonneux slwt->bpf.name = nla_memdup(tb[SEG6_LOCAL_BPF_PROG_NAME], GFP_KERNEL); 821004d4b27SMathieu Xhonneux if (!slwt->bpf.name) 822004d4b27SMathieu Xhonneux return -ENOMEM; 823004d4b27SMathieu Xhonneux 824004d4b27SMathieu Xhonneux fd = nla_get_u32(tb[SEG6_LOCAL_BPF_PROG]); 825004d4b27SMathieu Xhonneux p = bpf_prog_get_type(fd, BPF_PROG_TYPE_LWT_SEG6LOCAL); 826004d4b27SMathieu Xhonneux if (IS_ERR(p)) { 827004d4b27SMathieu Xhonneux kfree(slwt->bpf.name); 828004d4b27SMathieu Xhonneux return PTR_ERR(p); 829004d4b27SMathieu Xhonneux } 830004d4b27SMathieu Xhonneux 831004d4b27SMathieu Xhonneux slwt->bpf.prog = p; 832004d4b27SMathieu Xhonneux return 0; 833004d4b27SMathieu Xhonneux } 834004d4b27SMathieu Xhonneux 835004d4b27SMathieu Xhonneux static int put_nla_bpf(struct sk_buff *skb, struct seg6_local_lwt *slwt) 836004d4b27SMathieu Xhonneux { 837004d4b27SMathieu Xhonneux struct nlattr *nest; 838004d4b27SMathieu Xhonneux 839004d4b27SMathieu Xhonneux if (!slwt->bpf.prog) 840004d4b27SMathieu Xhonneux return 0; 841004d4b27SMathieu Xhonneux 842004d4b27SMathieu Xhonneux nest = nla_nest_start(skb, SEG6_LOCAL_BPF); 843004d4b27SMathieu Xhonneux if (!nest) 844004d4b27SMathieu Xhonneux return -EMSGSIZE; 845004d4b27SMathieu Xhonneux 846004d4b27SMathieu Xhonneux if (nla_put_u32(skb, SEG6_LOCAL_BPF_PROG, slwt->bpf.prog->aux->id)) 847004d4b27SMathieu Xhonneux return -EMSGSIZE; 848004d4b27SMathieu Xhonneux 849004d4b27SMathieu Xhonneux if (slwt->bpf.name && 850004d4b27SMathieu Xhonneux nla_put_string(skb, SEG6_LOCAL_BPF_PROG_NAME, slwt->bpf.name)) 851004d4b27SMathieu Xhonneux return -EMSGSIZE; 852004d4b27SMathieu Xhonneux 853004d4b27SMathieu Xhonneux return nla_nest_end(skb, nest); 854004d4b27SMathieu Xhonneux } 855004d4b27SMathieu Xhonneux 856004d4b27SMathieu Xhonneux static int cmp_nla_bpf(struct seg6_local_lwt *a, struct seg6_local_lwt *b) 857004d4b27SMathieu Xhonneux { 858004d4b27SMathieu Xhonneux if (!a->bpf.name && !b->bpf.name) 859004d4b27SMathieu Xhonneux return 0; 860004d4b27SMathieu Xhonneux 861004d4b27SMathieu Xhonneux if (!a->bpf.name || !b->bpf.name) 862004d4b27SMathieu Xhonneux return 1; 863004d4b27SMathieu Xhonneux 864004d4b27SMathieu Xhonneux return strcmp(a->bpf.name, b->bpf.name); 865004d4b27SMathieu Xhonneux } 866004d4b27SMathieu Xhonneux 867d1df6fd8SDavid Lebrun struct seg6_action_param { 868d1df6fd8SDavid Lebrun int (*parse)(struct nlattr **attrs, struct seg6_local_lwt *slwt); 869d1df6fd8SDavid Lebrun int (*put)(struct sk_buff *skb, struct seg6_local_lwt *slwt); 870d1df6fd8SDavid Lebrun int (*cmp)(struct seg6_local_lwt *a, struct seg6_local_lwt *b); 871d1df6fd8SDavid Lebrun }; 872d1df6fd8SDavid Lebrun 873d1df6fd8SDavid Lebrun static struct seg6_action_param seg6_action_params[SEG6_LOCAL_MAX + 1] = { 8742d9cc60aSDavid Lebrun [SEG6_LOCAL_SRH] = { .parse = parse_nla_srh, 8752d9cc60aSDavid Lebrun .put = put_nla_srh, 8762d9cc60aSDavid Lebrun .cmp = cmp_nla_srh }, 877d1df6fd8SDavid Lebrun 8782d9cc60aSDavid Lebrun [SEG6_LOCAL_TABLE] = { .parse = parse_nla_table, 8792d9cc60aSDavid Lebrun .put = put_nla_table, 8802d9cc60aSDavid Lebrun .cmp = cmp_nla_table }, 881d1df6fd8SDavid Lebrun 8822d9cc60aSDavid Lebrun [SEG6_LOCAL_NH4] = { .parse = parse_nla_nh4, 8832d9cc60aSDavid Lebrun .put = put_nla_nh4, 8842d9cc60aSDavid Lebrun .cmp = cmp_nla_nh4 }, 885d1df6fd8SDavid Lebrun 8862d9cc60aSDavid Lebrun [SEG6_LOCAL_NH6] = { .parse = parse_nla_nh6, 8872d9cc60aSDavid Lebrun .put = put_nla_nh6, 8882d9cc60aSDavid Lebrun .cmp = cmp_nla_nh6 }, 889d1df6fd8SDavid Lebrun 8902d9cc60aSDavid Lebrun [SEG6_LOCAL_IIF] = { .parse = parse_nla_iif, 8912d9cc60aSDavid Lebrun .put = put_nla_iif, 8922d9cc60aSDavid Lebrun .cmp = cmp_nla_iif }, 893d1df6fd8SDavid Lebrun 8942d9cc60aSDavid Lebrun [SEG6_LOCAL_OIF] = { .parse = parse_nla_oif, 8952d9cc60aSDavid Lebrun .put = put_nla_oif, 8962d9cc60aSDavid Lebrun .cmp = cmp_nla_oif }, 897004d4b27SMathieu Xhonneux 898004d4b27SMathieu Xhonneux [SEG6_LOCAL_BPF] = { .parse = parse_nla_bpf, 899004d4b27SMathieu Xhonneux .put = put_nla_bpf, 900004d4b27SMathieu Xhonneux .cmp = cmp_nla_bpf }, 901004d4b27SMathieu Xhonneux 902d1df6fd8SDavid Lebrun }; 903d1df6fd8SDavid Lebrun 904d1df6fd8SDavid Lebrun static int parse_nla_action(struct nlattr **attrs, struct seg6_local_lwt *slwt) 905d1df6fd8SDavid Lebrun { 906d1df6fd8SDavid Lebrun struct seg6_action_param *param; 907d1df6fd8SDavid Lebrun struct seg6_action_desc *desc; 908d1df6fd8SDavid Lebrun int i, err; 909d1df6fd8SDavid Lebrun 910d1df6fd8SDavid Lebrun desc = __get_action_desc(slwt->action); 911d1df6fd8SDavid Lebrun if (!desc) 912d1df6fd8SDavid Lebrun return -EINVAL; 913d1df6fd8SDavid Lebrun 914d1df6fd8SDavid Lebrun if (!desc->input) 915d1df6fd8SDavid Lebrun return -EOPNOTSUPP; 916d1df6fd8SDavid Lebrun 917d1df6fd8SDavid Lebrun slwt->desc = desc; 918d1df6fd8SDavid Lebrun slwt->headroom += desc->static_headroom; 919d1df6fd8SDavid Lebrun 920d1df6fd8SDavid Lebrun for (i = 0; i < SEG6_LOCAL_MAX + 1; i++) { 921d1df6fd8SDavid Lebrun if (desc->attrs & (1 << i)) { 922d1df6fd8SDavid Lebrun if (!attrs[i]) 923d1df6fd8SDavid Lebrun return -EINVAL; 924d1df6fd8SDavid Lebrun 925d1df6fd8SDavid Lebrun param = &seg6_action_params[i]; 926d1df6fd8SDavid Lebrun 927d1df6fd8SDavid Lebrun err = param->parse(attrs, slwt); 928d1df6fd8SDavid Lebrun if (err < 0) 929d1df6fd8SDavid Lebrun return err; 930d1df6fd8SDavid Lebrun } 931d1df6fd8SDavid Lebrun } 932d1df6fd8SDavid Lebrun 933d1df6fd8SDavid Lebrun return 0; 934d1df6fd8SDavid Lebrun } 935d1df6fd8SDavid Lebrun 936d1df6fd8SDavid Lebrun static int seg6_local_build_state(struct nlattr *nla, unsigned int family, 937d1df6fd8SDavid Lebrun const void *cfg, struct lwtunnel_state **ts, 938d1df6fd8SDavid Lebrun struct netlink_ext_ack *extack) 939d1df6fd8SDavid Lebrun { 940d1df6fd8SDavid Lebrun struct nlattr *tb[SEG6_LOCAL_MAX + 1]; 941d1df6fd8SDavid Lebrun struct lwtunnel_state *newts; 942d1df6fd8SDavid Lebrun struct seg6_local_lwt *slwt; 943d1df6fd8SDavid Lebrun int err; 944d1df6fd8SDavid Lebrun 9456285217fSDavid Lebrun if (family != AF_INET6) 9466285217fSDavid Lebrun return -EINVAL; 9476285217fSDavid Lebrun 948d1df6fd8SDavid Lebrun err = nla_parse_nested(tb, SEG6_LOCAL_MAX, nla, seg6_local_policy, 949d1df6fd8SDavid Lebrun extack); 950d1df6fd8SDavid Lebrun 951d1df6fd8SDavid Lebrun if (err < 0) 952d1df6fd8SDavid Lebrun return err; 953d1df6fd8SDavid Lebrun 954d1df6fd8SDavid Lebrun if (!tb[SEG6_LOCAL_ACTION]) 955d1df6fd8SDavid Lebrun return -EINVAL; 956d1df6fd8SDavid Lebrun 957d1df6fd8SDavid Lebrun newts = lwtunnel_state_alloc(sizeof(*slwt)); 958d1df6fd8SDavid Lebrun if (!newts) 959d1df6fd8SDavid Lebrun return -ENOMEM; 960d1df6fd8SDavid Lebrun 961d1df6fd8SDavid Lebrun slwt = seg6_local_lwtunnel(newts); 962d1df6fd8SDavid Lebrun slwt->action = nla_get_u32(tb[SEG6_LOCAL_ACTION]); 963d1df6fd8SDavid Lebrun 964d1df6fd8SDavid Lebrun err = parse_nla_action(tb, slwt); 965d1df6fd8SDavid Lebrun if (err < 0) 966d1df6fd8SDavid Lebrun goto out_free; 967d1df6fd8SDavid Lebrun 968d1df6fd8SDavid Lebrun newts->type = LWTUNNEL_ENCAP_SEG6_LOCAL; 969d1df6fd8SDavid Lebrun newts->flags = LWTUNNEL_STATE_INPUT_REDIRECT; 970d1df6fd8SDavid Lebrun newts->headroom = slwt->headroom; 971d1df6fd8SDavid Lebrun 972d1df6fd8SDavid Lebrun *ts = newts; 973d1df6fd8SDavid Lebrun 974d1df6fd8SDavid Lebrun return 0; 975d1df6fd8SDavid Lebrun 976d1df6fd8SDavid Lebrun out_free: 977d1df6fd8SDavid Lebrun kfree(slwt->srh); 978d1df6fd8SDavid Lebrun kfree(newts); 979d1df6fd8SDavid Lebrun return err; 980d1df6fd8SDavid Lebrun } 981d1df6fd8SDavid Lebrun 982d1df6fd8SDavid Lebrun static void seg6_local_destroy_state(struct lwtunnel_state *lwt) 983d1df6fd8SDavid Lebrun { 984d1df6fd8SDavid Lebrun struct seg6_local_lwt *slwt = seg6_local_lwtunnel(lwt); 985d1df6fd8SDavid Lebrun 986d1df6fd8SDavid Lebrun kfree(slwt->srh); 987004d4b27SMathieu Xhonneux 988004d4b27SMathieu Xhonneux if (slwt->desc->attrs & (1 << SEG6_LOCAL_BPF)) { 989004d4b27SMathieu Xhonneux kfree(slwt->bpf.name); 990004d4b27SMathieu Xhonneux bpf_prog_put(slwt->bpf.prog); 991004d4b27SMathieu Xhonneux } 992004d4b27SMathieu Xhonneux 993004d4b27SMathieu Xhonneux return; 994d1df6fd8SDavid Lebrun } 995d1df6fd8SDavid Lebrun 996d1df6fd8SDavid Lebrun static int seg6_local_fill_encap(struct sk_buff *skb, 997d1df6fd8SDavid Lebrun struct lwtunnel_state *lwt) 998d1df6fd8SDavid Lebrun { 999d1df6fd8SDavid Lebrun struct seg6_local_lwt *slwt = seg6_local_lwtunnel(lwt); 1000d1df6fd8SDavid Lebrun struct seg6_action_param *param; 1001d1df6fd8SDavid Lebrun int i, err; 1002d1df6fd8SDavid Lebrun 1003d1df6fd8SDavid Lebrun if (nla_put_u32(skb, SEG6_LOCAL_ACTION, slwt->action)) 1004d1df6fd8SDavid Lebrun return -EMSGSIZE; 1005d1df6fd8SDavid Lebrun 1006d1df6fd8SDavid Lebrun for (i = 0; i < SEG6_LOCAL_MAX + 1; i++) { 1007d1df6fd8SDavid Lebrun if (slwt->desc->attrs & (1 << i)) { 1008d1df6fd8SDavid Lebrun param = &seg6_action_params[i]; 1009d1df6fd8SDavid Lebrun err = param->put(skb, slwt); 1010d1df6fd8SDavid Lebrun if (err < 0) 1011d1df6fd8SDavid Lebrun return err; 1012d1df6fd8SDavid Lebrun } 1013d1df6fd8SDavid Lebrun } 1014d1df6fd8SDavid Lebrun 1015d1df6fd8SDavid Lebrun return 0; 1016d1df6fd8SDavid Lebrun } 1017d1df6fd8SDavid Lebrun 1018d1df6fd8SDavid Lebrun static int seg6_local_get_encap_size(struct lwtunnel_state *lwt) 1019d1df6fd8SDavid Lebrun { 1020d1df6fd8SDavid Lebrun struct seg6_local_lwt *slwt = seg6_local_lwtunnel(lwt); 1021d1df6fd8SDavid Lebrun unsigned long attrs; 1022d1df6fd8SDavid Lebrun int nlsize; 1023d1df6fd8SDavid Lebrun 1024d1df6fd8SDavid Lebrun nlsize = nla_total_size(4); /* action */ 1025d1df6fd8SDavid Lebrun 1026d1df6fd8SDavid Lebrun attrs = slwt->desc->attrs; 1027d1df6fd8SDavid Lebrun 1028d1df6fd8SDavid Lebrun if (attrs & (1 << SEG6_LOCAL_SRH)) 1029d1df6fd8SDavid Lebrun nlsize += nla_total_size((slwt->srh->hdrlen + 1) << 3); 1030d1df6fd8SDavid Lebrun 1031d1df6fd8SDavid Lebrun if (attrs & (1 << SEG6_LOCAL_TABLE)) 1032d1df6fd8SDavid Lebrun nlsize += nla_total_size(4); 1033d1df6fd8SDavid Lebrun 1034d1df6fd8SDavid Lebrun if (attrs & (1 << SEG6_LOCAL_NH4)) 1035d1df6fd8SDavid Lebrun nlsize += nla_total_size(4); 1036d1df6fd8SDavid Lebrun 1037d1df6fd8SDavid Lebrun if (attrs & (1 << SEG6_LOCAL_NH6)) 1038d1df6fd8SDavid Lebrun nlsize += nla_total_size(16); 1039d1df6fd8SDavid Lebrun 1040d1df6fd8SDavid Lebrun if (attrs & (1 << SEG6_LOCAL_IIF)) 1041d1df6fd8SDavid Lebrun nlsize += nla_total_size(4); 1042d1df6fd8SDavid Lebrun 1043d1df6fd8SDavid Lebrun if (attrs & (1 << SEG6_LOCAL_OIF)) 1044d1df6fd8SDavid Lebrun nlsize += nla_total_size(4); 1045d1df6fd8SDavid Lebrun 1046004d4b27SMathieu Xhonneux if (attrs & (1 << SEG6_LOCAL_BPF)) 1047004d4b27SMathieu Xhonneux nlsize += nla_total_size(sizeof(struct nlattr)) + 1048004d4b27SMathieu Xhonneux nla_total_size(MAX_PROG_NAME) + 1049004d4b27SMathieu Xhonneux nla_total_size(4); 1050004d4b27SMathieu Xhonneux 1051d1df6fd8SDavid Lebrun return nlsize; 1052d1df6fd8SDavid Lebrun } 1053d1df6fd8SDavid Lebrun 1054d1df6fd8SDavid Lebrun static int seg6_local_cmp_encap(struct lwtunnel_state *a, 1055d1df6fd8SDavid Lebrun struct lwtunnel_state *b) 1056d1df6fd8SDavid Lebrun { 1057d1df6fd8SDavid Lebrun struct seg6_local_lwt *slwt_a, *slwt_b; 1058d1df6fd8SDavid Lebrun struct seg6_action_param *param; 1059d1df6fd8SDavid Lebrun int i; 1060d1df6fd8SDavid Lebrun 1061d1df6fd8SDavid Lebrun slwt_a = seg6_local_lwtunnel(a); 1062d1df6fd8SDavid Lebrun slwt_b = seg6_local_lwtunnel(b); 1063d1df6fd8SDavid Lebrun 1064d1df6fd8SDavid Lebrun if (slwt_a->action != slwt_b->action) 1065d1df6fd8SDavid Lebrun return 1; 1066d1df6fd8SDavid Lebrun 1067d1df6fd8SDavid Lebrun if (slwt_a->desc->attrs != slwt_b->desc->attrs) 1068d1df6fd8SDavid Lebrun return 1; 1069d1df6fd8SDavid Lebrun 1070d1df6fd8SDavid Lebrun for (i = 0; i < SEG6_LOCAL_MAX + 1; i++) { 1071d1df6fd8SDavid Lebrun if (slwt_a->desc->attrs & (1 << i)) { 1072d1df6fd8SDavid Lebrun param = &seg6_action_params[i]; 1073d1df6fd8SDavid Lebrun if (param->cmp(slwt_a, slwt_b)) 1074d1df6fd8SDavid Lebrun return 1; 1075d1df6fd8SDavid Lebrun } 1076d1df6fd8SDavid Lebrun } 1077d1df6fd8SDavid Lebrun 1078d1df6fd8SDavid Lebrun return 0; 1079d1df6fd8SDavid Lebrun } 1080d1df6fd8SDavid Lebrun 1081d1df6fd8SDavid Lebrun static const struct lwtunnel_encap_ops seg6_local_ops = { 1082d1df6fd8SDavid Lebrun .build_state = seg6_local_build_state, 1083d1df6fd8SDavid Lebrun .destroy_state = seg6_local_destroy_state, 1084d1df6fd8SDavid Lebrun .input = seg6_local_input, 1085d1df6fd8SDavid Lebrun .fill_encap = seg6_local_fill_encap, 1086d1df6fd8SDavid Lebrun .get_encap_size = seg6_local_get_encap_size, 1087d1df6fd8SDavid Lebrun .cmp_encap = seg6_local_cmp_encap, 1088d1df6fd8SDavid Lebrun .owner = THIS_MODULE, 1089d1df6fd8SDavid Lebrun }; 1090d1df6fd8SDavid Lebrun 1091d1df6fd8SDavid Lebrun int __init seg6_local_init(void) 1092d1df6fd8SDavid Lebrun { 1093d1df6fd8SDavid Lebrun return lwtunnel_encap_add_ops(&seg6_local_ops, 1094d1df6fd8SDavid Lebrun LWTUNNEL_ENCAP_SEG6_LOCAL); 1095d1df6fd8SDavid Lebrun } 1096d1df6fd8SDavid Lebrun 1097d1df6fd8SDavid Lebrun void seg6_local_exit(void) 1098d1df6fd8SDavid Lebrun { 1099d1df6fd8SDavid Lebrun lwtunnel_encap_del_ops(&seg6_local_ops, LWTUNNEL_ENCAP_SEG6_LOCAL); 1100d1df6fd8SDavid Lebrun } 1101