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